纯函数
这里有一个纯函数示例
var z = 10;
function add(x, y){
return x + y
}
注意add函数没有去碰变量z,它没有读取z的值,也没有保存数据到z。它仅仅是读取参数x和y,也就是它的输入参数。
再看另一个函数
function justTen(){
return 10
}
这也是纯函数,但是是一个没有用的纯函数,因为它没有参数,唯一能做的就是返回一个常数,更好的做法将justTen定义成一个常量。再来看下面这个函数
function addNoReturn(x, y){
var z = x + y;
}
虽然也是纯函数,但是没有返回值,没有任何输出,是一个无用的函数。所以,所有有用的纯函数都应该返回一些东西
function add(x, y){
return x + y;
}
console.log(add(1, 2)); // 3
console.log(add(1, 2)); // 3
注意add(1, 2)的结果总是3,因为这是一个纯函数,如果add函数使用了外部的值,你根本不可能预测它的行为。纯函数对于给定相同的输入,总是产生相同的输出
由于纯函数不能修改任何外部变量,以下所有函数都是不纯的
writeFile(fileName);
sendAjaxRequest(ajaxRequest);
这些函数都有所谓的副作用,当你调用它们的时候,它们会修改文件和数据库表,所以你不能预测它们会发生什么。纯函数没有副作用
在函数式变成中,你不仅仅编写纯函数,我们目标是减少不纯代码的数量并将它们和我们程序中的其他部分隔离
函数的合成
如果一个值要经过多个函数,才能变成另一个值,可以把中间步骤合并成一个函数,这叫做函数的合成
上图中,X和Y之间的变形关系是函数f,Y和Z之间的变形关系是函数g,那么X和Z之间的关系,就是g和f的合成函数g·f。
const compose = function(f, g){
return function(x){
return f(g(x))
}
}
函数的合成还必须满足结合率
compose(f, compose(g, h));
// 等同于
compose(compose(f, g), h);
// 等同于
compose(f, g, h)
合成也是函数必须是纯的一个原因。因为一个不纯的函数,怎么跟其他函数合成?怎么保证各种合成以后,它会达到预期的行为?
前面说过,函数就像数据的管道(pipe)。那么,函数合成就是将这些管道连了起来,让数据一口气从多个管道中穿过。
柯里化
f(x)和g(x)合成为f(g(x)),有一个隐藏的前提,就是f和g都只能接收一个参数,如果可以接收多个参数,比如f(x, y)和g(a, b, c),函数合成就会非常麻烦
这时就需要函数柯里化了。所谓”柯里化”,就是把一个多参数的函数,转化为单参数函数。
function add(x, y){
return x + y;
}
add(1, 2) // 3
function Add(x){
// 对第一个参数没有进行处理,只是返回的函数中通过闭包拿到参数值
return function(y){
return x + y;
}
}
Add(1)(2)
有了柯里化以后,我们就能做到,所有函数只接受一个参数。后文的内容除非另有说明,都默认函数只有一个参数,就是所要处理的那个值。
函子
函数不仅可以用于同一个范畴之中值的转换,还可以用于将一个范畴转成另一个范畴。这就涉及到了函子(Functor)
函子是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位。
它首先是一种范畴,也就是说,是一个容器,包含了值和变形关系。比较特殊的是,它的变形关系可以依次作用于每一个值,将当前容器变形成另一个容器。
可以理解为值是输入的一组参数,变形关系是参数处理逻辑,函子将他们转化为另一组新数据。一般约定,函子的标志就是容器具有map方法。该方法将容器里面的每一个值,映射到另一个容器。学习函数式编程,实际上就是学习函子的各种运算。
接下来的原文太过理论化,等以后加深理解再补充