了解JavaScript函数式编程目录
- 0-了解 JavaScript 函数式编程 - 什么是纯函数
- 1-了解 JavaScript 函数式编程 - 柯里化
- 2-了解 JavaScript 函数式编程 - 代码组合的优势
- 3-了解 JavaScript 函数式编程 - 声明式函数
- 4-了解 JavaScript 函数式编程 - 类型签名
1、什么是 curry
curry
就是咖喱一样美好的工具性的拌料让我们的函数更加的易用、低耦合性。
curry
的概念很简单:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。 你可以一次性地调用 curry
函数,也可以每次只传一个参数分多次调用。
var add = function(x) {
return function(y) {
return x y;
};
};
var increment = add(1);
var addTen = add(10);
increment(2);
// 3
addTen(2);
// 12
复制代码
curry 的重要性
在这个多彩的世界,有些事物对与我们来说并不是非必须的,就像我们早已习惯存在但是又非必须的东西:互联网,移动手机,微波炉,电梯等等。当他们不存在的时候我们也能正常的快乐的生存下去,但是一旦拥有了以后他们的存在就变得不可或缺。就像我们的 curry 工具一样。
- 我们来创建一个普通的 curry ,for your enjoyment 吧。这里用到了 lodash 函数库,不熟悉的朋友可以看一下 lodash 的官网
var curry = require('lodash').curry;
var match = curry(function(what, str) {
return str.match(what);
});
var replace = curry(function(what, replacement, str) {
return str.replace(what, replacement);
});
var filter = curry(function(f, ary) {
return ary.filter(f);
});
var map = curry(function(f, ary) {
return ary.map(f);
});
复制代码
我在上面的代码中遵循的是一种简单,同时也非常重要的模式。即策略性地把要操作的数据(String, Array)放到最后一个参数里。到使用它们的时候你就明白这样做的原因是什么了。
- 下面我们开始使用上面的代码,看看为什么会这么去处理我们的函数。
// 匹配空格
match(/s /g, "hello world");
// [ ' ' ]
match(/s /g)("hello world");
// [ ' ' ]
// 引出一个 hasSpace 的函数变量,暂存用
var hasSpaces = match(/s /g);
// function(x) { return x.match(/s /g) }
// 使用这个 hasSpace 去做一些相关的处理
hasSpaces("hello world");
// [ ' ' ]
hasSpaces("spaceless");
// null
// 现在我们知道了它的返回值,我们可以通过其他函数做进一步的处理。比如筛选出一个有空格的数组值
filter(hasSpaces, ["tori_spelling", "tori amos"]);
// ["tori amos"]
// 保存这个 findSpace 的函数变量
var findSpaces = filter(hasSpaces);
// function(xs) { return xs.filter(function(x) { return x.match(/s /g) }) }
// 轻松的使用吧
findSpaces(["tori_spelling", "tori amos"]);
// ["tori amos"]
var noVowels = replace(/[aeiou]/ig);
// function(replacement, x) { return x.replace(/[aeiou]/ig, replacement) }
var censored = noVowels("*");
// function(x) { return x.replace(/[aeiou]/ig, "*") }
censored("Chocolate Rain");
// 'Ch*c*l*t* R**n'
代码语言:javascript复制我们为甚要这么多繁琐的步骤,而不是一步到位?
这里表明的是一种“预加载”函数的能力,通过传递一到两个参数调用函数,就能得到一个记住了这些参数的新函数。分解的使用的函数,让每个函数更具有一定的独立性,使用导出的时候,做到纯净无污染的传递。
扩展我们的 curry
curry
的用处非常广泛,就像在 hasSpaces
、findSpaces
和 censored
看到的那样,只需传给函数一些参数,就能得到一个新函数。
- 下面我们用
map
包裹一下我们的函数
var getChildren = function(x) {
return x.childNodes;
};
var allTheChildren = map(getChildren);
复制代码
只传给函数一部分参数这样的操作通常被称为局部调用(partial application),因为只需要内联调用能够大量减少样板文件代码(boilerplate code)。
当我们谈论纯函数的时候,我们说它们接受一个输入返回一个输出。curry 函数所做的正是这样:每传递一个参数调用函数,就返回一个新函数处理剩余的参数。这就是一个输入对应一个输出啊。
练习一下
- 这里引用了 ramda,如果没有的话可以手动引入安装一下和引用。
npm install ramda
var _ = require('ramda');
// 练习 1(局部调用的使用)
//==============
// 通过局部调用(partial apply)移除所有参数
var words = function(str) {
return split(' ', str);
};
// 练习 1a(组合使用函数)
//==============
// 使用 `map` 创建一个新的 `words` 函数,使之能够操作字符串数组
var sentences = undefined;
// 练习 2
//==============
// 通过局部调用(partial apply)移除所有参数
var filterQs = function(xs) {
return filter(function(x){ return match(/q/i, x); }, xs);
};
// 练习 3(柯里化~)
//==============
// 使用帮助函数 `_keepHighest` 重构 `max` 使之成为 curry 函数
// 无须改动:
var _keepHighest = function(x,y){ return x >= y ? x : y; };
// 重构这段代码:
var max = function(xs) {
return reduce(function(acc, x){
return _keepHighest(acc, x);
}, -Infinity, xs);
};
// use curry 1:
// ============
// 包裹数组的 `slice` 函数使之成为 curry 函数
// //[1,2,3].slice(0, 2)
var slice = undefined;
// use curry 2:
// ============
// 借助 `slice` 定义一个 `take` curry 函数,该函数调用后可以取出字符串的前 n 个字符。
var take = undefined;
复制代码
这是上面的答案 Q&A,先别急着看答案,让我们先思考一下
总结
通过简单地传递几个参数,就能动态创建实用的新函数;而且还能带来一个额外好处,那就是保留了数学的函数定义,尽管参数不止一个。
下篇链接 了解 JavaScript 函数式编程 - 代码组合的优势
参考
- JS 函数式编程指南
- ramda官网
- loadsh官网