不仅仅是双关语/咖喱

curry 的用处非常广泛,就像在 hasSpacesfindSpacescensored 看到的那样,只需传给函数一些参数,就能得到一个新函数。

map 简单地把参数是单个元素的函数包裹一下,就能把它转换成参数为数组的函数。

  1. var getChildren = function(x) {
  2. return x.childNodes;
  3. };
  4. var allTheChildren = map(getChildren);

只传给函数一部分参数通常也叫做局部调用(partial application),能够大量减少样板文件代码(boilerplate code)。考虑上面的 allTheChildren 函数,如果用 lodash 的普通 map 来写会是什么样的(注意参数的顺序也变了):

  1. var allTheChildren = function(elements) {
  2. return _.map(elements, getChildren);
  3. };

通常我们不定义直接操作数组的函数,因为只需内联调用 map(getChildren) 就能达到目的。这一点同样适用于 sortfilter 以及其他的高阶函数(higher order function)(高阶函数:参数或返回值为函数的函数)。

当我们谈论纯函数的时候,我们说它们接受一个输入返回一个输出。curry 函数所做的正是这样:每传递一个参数调用函数,就返回一个新函数处理剩余的参数。这就是一个输入对应一个输出啊。

哪怕输出是另一个函数,它也是纯函数。当然 curry 函数也允许一次传递多个参数,但这只是出于减少 () 的方便。

总结

curry 函数用起来非常得心应手,每天使用它对我来说简直就是一种享受。它堪称手头必备工具,能够让函数式编程不那么繁琐和沉闷。

通过简单地传递几个参数,就能动态创建实用的新函数;而且还能带来一个额外好处,那就是保留了数学的函数定义,尽管参数不止一个。
下一章我们将学习另一个重要的工具:组合(compose)。

第 5 章: 代码组合(compose)

练习

开始练习之前先说明一下,我们将默认使用 ramda 这个库来把函数转为 curry 函数。或者你也可以选择由 lodash 的作者编写和维护的 lodash-fp。这两个库都很好用,选择哪一个就看你自己的喜好了。

你还可以对自己的练习代码做单元测试,或者把代码拷贝到一个 REPL 里运行看看。

这些练习的答案可以在本书仓库中找到。

  1. var _ = require('ramda');
  2. // 练习 1
  3. //==============
  4. // 通过局部调用(partial apply)移除所有参数
  5. var words = function(str) {
  6. return split(' ', str);
  7. };
  8. // 练习 1a
  9. //==============
  10. // 使用 `map` 创建一个新的 `words` 函数,使之能够操作字符串数组
  11. var sentences = undefined;
  12. // 练习 2
  13. //==============
  14. // 通过局部调用(partial apply)移除所有参数
  15. var filterQs = function(xs) {
  16. return filter(function(x){ return match(/q/i, x); }, xs);
  17. };
  18. // 练习 3
  19. //==============
  20. // 使用帮助函数 `_keepHighest` 重构 `max` 使之成为 curry 函数
  21. // 无须改动:
  22. var _keepHighest = function(x,y){ return x >= y ? x : y; };
  23. // 重构这段代码:
  24. var max = function(xs) {
  25. return reduce(function(acc, x){
  26. return _keepHighest(acc, x);
  27. }, -Infinity, xs);
  28. };
  29. // 彩蛋 1:
  30. // ============
  31. // 包裹数组的 `slice` 函数使之成为 curry 函数
  32. // //[1,2,3].slice(0, 2)
  33. var slice = undefined;
  34. // 彩蛋 2:
  35. // ============
  36. // 借助 `slice` 定义一个 `take` curry 函数,该函数调用后可以取出字符串的前 n 个字符。
  37. var take = undefined;