引用 本文对刚刚在网络上现身的 JavaScript 函数式编程库 functional.js 进行详尽的解读(地址已更正,感谢 hax)。
functional.js 是模仿 Haskell 语言标准库 Prelude 制作的函数式编程库,主要实现了:
- 扩展的克里化函数
- 运算符函数化
- 紧缩的匿名函数语法
- 无须指定参数的匿名函数语法
- 函数向导语法
- 基本的通用列表操作
- 部分扩展基于对象化
其中,扩展语法由字符串表示。未能实现的特性有:
- 尾递归优化
- 模式匹配(包括参数匹配、列表匹配、情况分析)
- 惰性运算(包括无穷列表)
- 列表领悟
- 扩展绑定、同时绑定
- 其它列表操作(以及对于列表操作的基于对象化)
下面我们一边分析源代码,一边讲解库的用法。
一、库安装和概览
functional.js 库的所有用户级操作分为3个部分:
- 全局操作,绑定在全局对象
Functional 上,主要是高阶函数操作和列表操作,所谓库安装即把这些内容可选的、安全地复制到全局环境
- 函数扩展,实现特殊高阶函数特性的工具(特供内部)
- 语法扩展,绑定在
String.prototype 上,负责将字符串表示的 lambda 语法翻译为相应的高阶函数(特供内部)
下面是安装函数 Functional.install 的源代码(中文注释为笔者所加,下同):
代码
一般只要执行 Functional.install() 一句即可。
二、高阶函数操作
1. Functional.compose ([Function]) // 匿名的参数类型指的是 arguments 的类型,下同
接受一列参数个数被认为相等的(允许 Currying 算子)函数为参数,返回一个函数,它接受一定的参数,能够对它们累积倒序 apply 那列函数。
示例:compose('1+', '2*')(2) => 5
2. Functional.sequence ([Function])
累积 apply 的顺序为参数顺序,为 compose 的反序。
示例:sequence('1+', '2*')(2) => 6
以上两个操作亦可见于 Function.prototype,用法:'1+'.lambda().sequence('2*')(2) ==> 6
3. Function.prototype.flip ()
返回一个函数,是原函数对象 this 参数接受顺序颠倒后的版本,不应属于函数扩展类。
示例:flip('a/b')(1, 2) => 2
4. Function.prototype.saturate ([]) {
返回一个函数,是原函数对象 this 忽略自己接受的参数,仅接受指定参数的版本。
形式:f.saturate(args...)(args2...) == f(args...)
5. Function.prototype.aritize (n::Number) // 有名的参数类型由 :: 指定,下同
返回一个函数,是原函数对象 this 忽略自己接受的参数列表中下标为 n 的参数的版本。
6. Function.S (f, g::Function)
以单个大写字母命名的是函数的原子操作,应被收入 Functional 对象,这个很奇怪。
形式:S(f, g)(args...) == f(g(args...), args...)
三、通用列表操作
1. 绑定在 Functional 对象上的部分完全照抄 Haskell Prelude 以及 Clean 的命名,它们是:
- map(f,
[x1, x2...]) = [f(x, 0), f(x2, 1), ...]
- foldl,
reduce(f, init, [x0, x1, x2]) == f(f(f(init, x0), x1), x2)
- filer,
select('%2', [1,2,3,4]) -> [1, 3]
- foldr(f,
init, [x0, x1, x2]) == fn(x0, f(x1, f(x2, init)))
- some(f,
[x1, x2, x3, ...]) == f(x1) || f(x2) || f(x3)...
- every(f,
[x1, x2, x3, ...]) == f(x1) && f(x2) && f(x3)...
以上操作的介绍网上到处都是,不再赘述;但有一点不同,即它们除了接受正常参数之外,还在最后接受一个可选参数 object::Object,它被用于指定操作执行的对象/环境。
另外,这些操作全部基于命令式风格实现,对于没有尾递归优化的 JavaScript 来说,效率有保障。
四、群体谓词操作
1. Functional.and ([Function])
接受一列函数为参数,返回一个函数,它接受一个参数,对该参数 apply 那列函数,如结果全为 true,返回 true;否则返回 false。
形式:and(f1, f2...)(args...) == f1(args...) && f2(args...)...
示例:and('>1', '>2')(2) => false
2. Functional.or ([Function])
接受一列函数为参数,返回一个函数,它接受一个参数,对该参数 apply 那列函数,如结果全为 false,返回 false;否则返回 true。
形式:or(f1, f2...)(args...) == f1(args...) || f2(args...)...
示例:or('>1', '>2')(2) => true
3. Functional.not = function(fn::Function)
返回一个函数,是参数返回的布尔值的函数(谓词,下同) fn 取否的版本。
形式:f.not()(args...) == !f(args...)
4. Functional.equal ([Function])
接受一列函数为参数,返回一个函数,它接受一个参数,对该参数 apply 那列函数,如结果全部 == ,返回 true;否则返回 false。
形式:equal(f1, f2...)(args...) == f1(args...) == f2(args...)...
示例:equal()() => true // 特殊情况
五、函数扩展
这一章仅仅是介绍内部实现。
1. Function.prototype.bind (object::Object,[])
返回一个函数,作为 this 函数对象的副本,使其将在 object 环境下执行,并额外携带参数。
形式:f.bind(obj, args...)(args2...) == f.apply(obj, [args..., args2...])
2. Function.prototype.curry ([])
这是实现克里化特性的关键函数,思想来自网络。
代码
返回那个传说中的可在参数不足时分步调用的函数——Currying 算子。
形式:f.curry(args1...)(args2...) == f(args1..., args2...)
其它的 curry 类函数有:
- rcurry,对从右边开始缺少参数的函数作克里化
- ncurry,不接受全部参数就不
apply 参数的版本
- rncurry,前者的反序版本
- uncurry,作者一再强调,这不是
curry 的反转版本。它会拆分出第一个已得参数,形式为:f.uncurry(a, b...) == f(a)(b...)
3. Function.prototype.partial ([])
在此函数定义之前,有定义 _ = Function._ = {} 。结合它们可以允许你像在 Haskell 中那样在参数列表中用 _ 忽略参数。但现在空谈是没用的,要结合第七章的语法扩展。
4. Function.prototype.guard (guard:>Function, otherwise)
类似的,是一个允许在函数定义中使用向导功能的工具,尚缺少语法扩展支持。
形式:f.guard(g, h)(args...) == f(args...), when g(args...) is true
f.guard(g ,h)(args...) == h(args...), when g(args...) is false
六、工具函数
1. Functional.invoke (methodName::String, [])
示例:invoke('toString')(123) => "123"
2. Functional.pluck (name::String)
示例:pluck('length')("abc") => 3
3. Functional.until (pred:>Function, fn:>Function) // 用 :> 表示将参数强制转换类型
代码
类似 Haskell 的 until,是一种函数式的循环,用命令式风格实现。
4. Functional.zip ([])
特别注意,此 zip 并非 Haskell 中的 zip,它接受可变参数列表而不是列表的列表。
形式:zip(a, b...) == [[a0, b0], [a1, b1], ...]
以上的章节中绑定在 Functional 上函数都可作为 Function 的对象方法直接使用,我们看这一行:
代码
前文对它们作出了定义,这里忽略。用法:name(arg, args...) == arg.name(args...)。
七、语法扩展
1. String.prototype.lambda ()
把字符串表示的字符串翻译为函数扩展可接受的函数,进一步转为 JavaScript 函数。
代码
八、过滤器生成器
仅供特别好学的同志们参考。
1. Function.prototype.prefilterObject (filter::Function)
形式:fn.prefilterObject(filter).apply(object, args...) == fn.apply(filter(object), args...)
2. Function.prototype.prefilterAt (index::Number, filter::Function)
形式:fn.prefilterAt(i, filter)(a1, a2, ..., a_{n}) == fn(a1, a2, ..., filter(a_{i}), ..., a_{n})
3. Function.prototype.prefilterSlice (filter::Function, start, end::Number)
形式:fn.prefilterSlice(i0, i1, filter)(a1, a2, ..., a_{n}) == fn(a1, a2, ..., filter(args_{i0}, ..., args_{i1}), ..., a_{n})
九、其它用户级函数
1. Functional.id = Functional.I = function(x) {return x};
2. Functional.constfn = Functional.K = function(x) {return function() {return x}};
3. .toFunction ()
在 String.prototype,Function.prototype,Function(需要参数 fn::Function) 上都有绑定,把对象转换为一个合适的 Functional 函数。但你不需要把代码写这样,map('*2'.toFunction(),alist),因为全局用户级函数都会对应为函数的参数自动执行 toFunction(),只要 map('*2',alist) 就行了。另外,String.prototype 上还有 JavaScript-Like 的 call、apply 方法。
十、结语
functional.js 很强,很有用,很牛X;但同时也很年轻(7.20 发布),很多可以实现的功能还不完善,不说列表领悟什么的吧,至少应该把 Haskell Prelude 库在通用列表操作方面的函数的移植工作完成。我们期待 Oliver Steele 的表现。 |
相关推荐
在Underscore的源码分析过程中,可以学习到如何编写简洁、高效的代码,同时深化对JavaScript基础知识的理解。此外,尽管Lodash在性能和功能上都超越了Underscore,但Underscore源码的短小精悍更适合JavaScript开发者...
**标题解析:** "baconjsFRPfunctionalreactiveprogrammingJS编程库" 指的是 Bacon.js,一个基于 Functional Reactive Programming(函数响应式编程,简称FRP)的JavaScript编程库。 **描述详解:** "bacon.js - ...
React天气应用源码分析 React天气应用是一款基于React.js开发的Web应用程序,它允许用户查看当前及未来的天气信息。在这款应用中,React作为一个强大的JavaScript库,被用来构建用户界面,提供高效、可复用的组件。...
后台功能主要包括用户管理(注册、登录、权限控制)、场地资源管理(发布、预订、状态更新)、预约处理(确认、取消、超时处理)和数据分析(使用统计、优化策略)。通过合理的业务逻辑设计和数据库操作,实现系统...
Java 8增加了对类型注解的支持,可以在类型声明(如类、接口、方法和字段)上使用注解,这对于编译时检查和静态分析工具有很大帮助。 以上就是Java 8源码中包含的一些关键特性。通过深入学习和理解这些特性,...
Functional-UI5 示例是基于JavaScript开发的一个项目,它主要用于展示如何在SAP UI5框架下构建功能丰富的...同时,通过对源码的分析和调试,可以提升开发者解决实际问题的能力,为未来开发更复杂的应用打下坚实基础。
end platform through the springboot framework structure and Java programming language, realizes the presentation and feedback of data information through the web server, and the main functional ...
这将是一个很好的学习素材,因为通过阅读和分析源码,我们可以深入理解各种技术的实际应用和最佳实践。 【标签】中的关键词提供了更多具体的技术方向: 1. **React**:这是一个流行的JavaScript库,用于构建用户...
JDK 8增加了类型注解,允许在类型声明(包括类、接口、方法和变量)上使用注解,增强了静态分析和编译时检查的能力。 **8. Parallel GC优化** 在JDK 8中,垃圾收集器进行了优化,特别是Parallel GC(并行垃圾回收)...
4. **Node.js和服务器端处理**:使用Node.js可以处理后台任务,如模型预处理、仿真计算和结果分析。 5. **前端框架**:如React或Vue.js,可以用于构建用户友好的交互界面,展示模型参数,启动和控制仿真。 6. **...
这些特性使得 Java 8 成为一个里程碑式的更新,不仅提高了开发效率,也使 Java 适应了更广泛的应用场景,如云计算、大数据分析等。通过深入研究 "java8-src-sample" 中的源码示例,开发者可以更好地理解和应用这些...
通常,这样的结构会包括README文件(介绍项目)、源码目录、测试文件、配置文件等。 综合这些信息,我们可以预期这个项目会包含以下知识点: 1. **PureScript基础**: PureScript的语法、类型系统、模块系统和如何...
而“FP”是“Functional Programming”的缩写,是一种编程范式,它强调将计算视为函数的数学运算,而不是对状态或数据流的改变。FP倾向于避免副作用,推崇纯函数,以及使用高阶函数和不可变数据结构。 在这份名为...
由于其特性,OCaml常用于编译器、操作系统、数据分析和金融软件等领域。 **Elm到OCaml的编译过程** "philip2"编译器的工作原理可能是将Elm源代码转换为中间表示(IR),然后将这个IR翻译成OCaml代码。这个过程中,...