Currying或者Curry, 中文有翻译成科里化. 我最早了解它是在一篇讲Groovy中函数式编程的文章中, 之后又在Python中遇到同样的东西. 最近在看Scala的介绍时又看到了, 而且发现Scala设计的明显更好, 然后就成了这篇文章, 使用Javascript作为主要语言是因为我使用Javascript的时间更长, 并且Javascript这门语言的表达能力奇强^-^.
我不确定把Currying记作科里化是否更容易理解, 所以下文还是依旧使用Currying吧.
Currying是函数式编程中一种高阶函数的典型应用, 如果非要把它对应到传统OO中的话, 那么它类似Builder模式, 一般译作构建器模式 建造者模式.
Builder模式可以简单理解为创建一个复杂的对象需要依赖多个参数, 要提供的参数又依赖于不同的方法, 使用Builder模式让每个方法只关注自己提供的参数, 最终根据全部参数创建出对象来. 对象实例最终是拿来调用的, 可以把这个过程想象成调用一个参数很多的函数.
Javascript中完全可以按照传统OO的方式实现Builder模式, 但使用Currying更轻量级 简单, 考虑下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
执行输出
1 2 |
|
modN函数有2个参数, 示例中可以同时提供所有的参数, 当然这是相当理想的情况. 实际可能不同的参数在不同的阶段提供:
1 2 3 4 5 6 7 8 9 10 11 |
|
这种方式需要有一个变量用来保存参数, 而如果使用Currying可以这样:
1 2 |
|
根据需要可以curry()
多个参数或curry()
多次. 上文中使用的curry
函数的实现:
1 2 3 4 5 6 7 |
|
这是最轻量级 最简单的实现方式, 深入挖掘Javascript语言的表达力应该还会出现更巧妙的设计.
Currying的来由
考虑体积计算公式体积 = 长 x 宽 x 高
, 假设已知长为10, 那么这个公式就变成了体积 = 10 x 宽 x 高
, 进一步已知宽为7, 那么公式就变为体积 = 10 x 7 x 高
, 这种转换即Currying.
这是最通俗的描述, 比较正式的可以参考维基百科的Currying词条.
Scala语言中的Currying
之所以要额外提Scala, 是因为它是原生支持Currying的语言, 相对比通过类库支持能提供更巧妙的语法, 参见下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
上述代码只是加了额外的注释, 调整了下顺序, 和来源中的代码等价.
最重要的, Scala是静态类型语言, 开发环境可以提供每次Currying
之后的函数提示信息, 并能够做编译时检查, 而Groovy, Python, Javascript只能依赖约定, 错误会在运行时发生, 必须有其它的措施确保同步修改关联的代码.
偏函数 Partial function
可以译作偏函数, 即提供部分参数值的函数, 和Currying很像, 只是另外一种更灵活的语法, 可以不按照参数顺序提供参数.
在John Resig的Blog有一篇介绍Partial function和Currying的文章, 贴个简单的代码片段:
1 2 3 4 5 |
|
partial()
的实现:
1 2 3 4 5 6 7 8 9 10 |
|
里面还有一个curry
函数, 基本和上文中的差不多, 有兴趣的可以点Partial Application in JavaScript
Scala也有Partial function的实现, 依旧是静态类型, 示例代码:
1 2 3 |
|
参考[Wikipedia][]的Contrast with partial function application 和 Partial function
Javascript中更常见的传递大量参数的方式
Javascript是动态语言, 开发环境无法提供太多提示信息, 上文提到的Currying更适合一些比较稳定的, 不经常变动的API.
实际项目中如果函数参数很多, 并且可能在不同的地方提供参数, 则会使用参数对象的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
这个看起来和Builder模式十分相像, 参数都提供有默认值, num
和x
可以使用更有意义的名称以使阅读性更好一些, 但也付出了不少编码工作.
从Firefox 2.0开始支持解构赋值New in JavaScript 1.7: Destructuring assignment, 这个特性可以让实现modN
少了一些纠结:
1 2 3 4 5 6 |
|
原文中还有解构赋值的很多高级用法, 但是到目前为之还没看到其它浏览器提供支持, 也没有进入ECMAScript标准, 只能在比如Firefox扩展开发时爽爽.
参考资料
- Lambda演算与科里化(Currying)
- 《JavaScript王者归来》第二十二章 科里化(Currying)小节
- 实战 Groovy: 用 curry 过的闭包进行函数式编程
- curry – associating parameters with a function (Python recipe)
- A Tour of Scala: Currying
- 跨越边界: JavaScript 语言特性
- 维基百科 高阶函数 词条
- 维基百科 Currying 词条
- 维基百科 Partial function 词条
- New in JavaScript 1.7 : Destructuring assignment 解构赋值