一、资源
函数式编程指南
阮一峰函数式编程入门教程
概念
函数式编程提倡 利用若干简单的执行单元 让计算结果不断渐进,逐层推导复杂的运算。
函数式两个最基本的运算:合成+柯里化。
二、compose(合成)
如果一个值要经过多个函数,才能变成另外一个值,就可以把所有中间步骤合并成一个函数,这叫做"函数的合成"(compose)。
合成的好处:
1、代码简单富有可读性;
2、同时通过不同的组合方式,可以轻易组合出其他常用函数,让我们的代码更具表现力。
隐蔽规则:
有一个隐藏的前提,就是
f和g都只能接受一个参数。如果可以接受多个参数,比如f(x, y)和g(a, b, c),函数合成就非常麻烦。 这时就需要函数柯里化了。所谓"柯里化",就是把一个多参数的函数,转化为单参数函数。
function f1(arg) {
console.log(arg+'f1');
return arg;
}
function f2(arg) {
console.log(arg+'f2');
return arg;
}
function f3(arg) {
console.log(arg+'f3');
return arg;
}
function compose (...fns) {
return fns.reduce((a,b)=>(...args)=>a(b(...args)));
}
compose(f1,f2,f3)('hello');
//hellof3
//hellof2
//hellof1
//"hello"
函数就像数据的管道(pipe)。那么,函数合成就是将这些管道连了起来,让 数据一口气从多个管道中穿过。
三、Currying(柯里化)
所谓"柯里化",就是把一个多参数的函数,转化为单参数函数。
// 柯里化之前
function add(x, y) {
return x + y;
}
add(1, 2) // 3
// 柯里化之后
function addX(y) {
return function (x) {
return x + y;
};
}
addX(2)(1) // 3
实际使用:
const addx = (a)=>(b)=>(a+b);
const addOne = addx(1);
const addTen = addx(10);
const compose = (...fns)=>fns.reduce((a,b)=>(...args)=>a(b(...args)));
compose(addOne, addTen)(20);
// 31
四、函子
任何具有map方法的数据结构,都可以当作函子的实现。
class Functor {
constructor(val) {
this.val = val;
}
map(f) {
return new Functor(f(this.val));
}
}
上面代码中,Functor是一个函子,它的map方法接受函数f作为参数,然后返回一个新的函子,里面包含的值是被f处理过的(f(this.val))。
一般约定,函子的标志就是容器具有map方法。该方法将容器里面的每一个值,映射到另一个容器。
下面是一些用法的示例。
(new Functor(2)).map(function (two) {
return two + 2;
});
// Functor(4)
(new Functor('flamethrowers')).map(function(s) {
return s.toUpperCase();
});
// Functor('FLAMETHROWERS')
(new Functor('bombs')).map(_.concat(' away')).map(_.prop('length'));
// Functor(10)
上面的例子说明,函数式编程里面的运算,都是通过函子完成,即运算不直接针对值,而是针对这个值的容器----函子。函子本身具有对外接口(map方法),各种函数就是运算符,通过接口接入容器,引发容器里面的值的变形。
因此,学习函数式编程,实际上就是学习函子的各种运算。
由于可以把运算方法封装在函子里面,
所以又衍生出各种不同类型的函子,
有多少种运算,就有多少种函子。
函数式编程就变成了运用不同的函子,解决实际问题。
五、of
函数式编程一般约定,函子有一个of方法,用来生成新的容器。
Functor.of = function(val) {
return new Functor(val);
};
然后,前面的例子就可以改成下面这样。
Functor.of(2).map(function (two) {
return two + 2;
});
// Functor(4)
这就更像函数式编程了。