序
命令式编程中“典型”的方法和过程都深深地根植于它们所在的环境中,通过状态、依赖和有效作用达成;纯函数与此相反,它与环境无关,只要我们愿意,可以在任何地方运行它
面向对象语言的问题是,它们永远都要随身携带那些隐式的环境。你只需要一个香蕉,但却得到一个拿着香蕉的大猩猩...以及整个丛林
一等公民的函数
可作为变量一样被传递、返回或者在函数中嵌套函数、可作为参数、使用总有返回值的表达式而不是语句。
纯函数应该天然,无副作用
纯函数是这样一种函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用。
副作用是指,函数内部与外部互动,产生运算以外的其他结果。 例如在函数调用的过程中,利用并修改到了外部的变量,那么就是一个有副作用的函数。
概括而言,副作用包含:
- 改变了任何外部变量或对象属性(例如,全局变量,或者一个在父级函数作用域链上的变量)
- 写日志
- 在屏幕输出
- 写文件
- 发网络请求
- 触发任何外部进程
- 调用另一个有副作用的函数
比如 slice
和 splice
,这两个函数的作用并无二致——但是注意,它们各自的方式却大不同,但不管怎么说作用还是一样的。我们说 slice 符合纯函数的定义是因为对相同的输入它保证能返回相同的输出。而 splice
却会嚼烂调用它的那个数组,然后再吐出来;这就会产生可观察到的副作用,即这个数组永久地改变了。
var xs = [1,2,3,4,5];
// 纯的
xs.slice(0,3);
//=> [1,2,3]
xs.slice(0,3);
//=> [1,2,3]
xs.slice(0,3);
//=> [1,2,3]
// 不纯的
xs.splice(0,3);
//=> [1,2,3]
xs.splice(0,3);
//=> [4,5]
xs.splice(0,3);
//=> []
在函数式编程中,我们讨厌这种会改变数据的笨函数。我们追求的是那种可靠的,每次都能返回同样结果的函数,而不是像 splice 这样每次调用后都把数据弄得一团糟的函数,这不是我们想要的。
来看看另一个例子。
// 不纯的
var minimum = 21;
var checkAge = function(age) {
return age >= minimum;
};
// 纯的
var checkAge = function(age) {
var minimum = 21;
return age >= minimum;
};
柯里化(curry)
把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下参数且返回结果的新函数
function sub_curry(fn) {
var args = [].slice.call(arguments, 1);
return function() {
return fn.apply(this, args.concat([].slice.call(arguments)));
};
}
function curry(fn, length) {
length = length || fn.length;
var slice = Array.prototype.slice;
return function() {
if (arguments.length < length) {
var combined = [fn].concat(slice.call(arguments));
return curry(sub_curry.apply(this, combined), length - arguments.length);
} else {
return fn.apply(this, arguments);
}
};
}
// 我们验证下这个函数:
var fn = curry(function(a, b, c) {
return [a, b, c];
});
fn("a", "b", "c") // ["a", "b", "c"]
fn("a", "b")("c") // ["a", "b", "c"]
fn("a")("b")("c") // ["a", "b", "c"]
fn("a")("b", "c") // ["a", "b", "c"]
上面例子,初次看时,可能会不太明白,这里我们拿fn("a")("b")("c")
逐步解析下。
curry
传入函数并且赋值给fn
。curry
函数中length
为3
后返回一个函数。- 当
fn("a")("b")("c")
时,执行上一步骤返回的函数,即curry() { return function() {....}}
,在这里为了方便,将curry
返回的函数称为fun
fn("a")
时,fun
的arguments.length
为1,故执行if语句的代码块。- 将
function(a, b, c) {return [a, b, c];}
函数作为数组的参数值与fun
的arguments
链接( [fn].concat(slice.call(arguments) ) 。 - 回调
curry
,curry
的第一个参数为sub_curry
函数返回的值,其作用是携带function(a, b, c){...}
函数与a
值。第二个参数 作用差多少个"参数"。 - 重复步骤2以后步骤(但此时
length
为2
)
当然了,如果你觉得还是无法理解,你可以选择下面这种实现方式,可以实现同样的效果:
function curry(fn, args) {
let length = fn.length;
args = args || [];
return function() {
let _args = args.slice(0)
for (let i = 0; i < arguments.length; i++) {
_args.push(arguments[i])
}
if (_args.length < length) {
return curry.call(this, fn, _args)
} else {
return fn.apply(this, _args)
}
}
}
var fn = curry(function(a, b, c) {
console.log([a, b, c])
});
fn("a", "b", "c") // ["a", "b", "c"]
fn("a", "b")("c") // ["a", "b", "c"]
fn("a")("b")("c") // ["a", "b", "c"]
fn("a")("b", "c") // ["a", "b", "c"]
或许大家觉得这种方式更好理解,又能实现一样的效果,为什么不直接就讲这种呢?
因为想给大家介绍各种实现的方法嘛,不能因为难以理解就不给大家介绍呐~
代码组合(compose)
函数饲养
这就是 组合(compose,以下将称之为组合):
var compose = function(f,g) {
return function(x) {
return f(g(x));
};
};
f 和 g 都是函数,x 是在它们之间通过“管道”传输的值。
组合看起来像是在饲养函数。你就是饲养员,选择两个有特点又遭你喜欢的函数,让它们结合,产下一个崭新的函数。组合的用法如下:
var toUpperCase = function(x) { return x.toUpperCase(); };
var exclaim = function(x) { return x + '!'; };
var shout = compose(exclaim, toUpperCase);
shout("send in the clowns");
//=> "SEND IN THE CLOWNS!"
两个函数组合之后返回了一个新函数是完全讲得通的:组合某种类型(本例中是函数)的两个元素本就该生成一个该类型的新元素。把两个乐高积木组合起来绝不可能得到一个林肯积木。所以这是有道理的,我们将在适当的时候探讨这方面的一些底层理论。
在 compose 的定义中,g 将先于 f 执行,因此就创建了一个从右到左的数据流。这样做的可读性远远高于嵌套一大堆的函数调用,如果不用组合,shout 函数将会是这样的:
var shout = function(x){
return exclaim(toUpperCase(x));
};
我们再看看别的例子
var compose = function (f, g) {
return function (x) {
return f(g(x));
};
}
var add = function (x) {
let y = x + 1
console.log('add', y)
return y
}
var reduce = function (x) {
let y = x - 1
console.log('reduce', y)
return y
}
var multiplication = function (x) {
let y = x * 2
console.log('multiplication', y)
return y
}
// var fruit = compose(reduce, compose(add, multiplication)); // 200
var fruit = compose(compose(reduce, add), multiplication); // 200
fruit(100)
现在是时候去看看所有的组合都有的一个特性了。
// 结合律(associativity)
var associative = compose(f, compose(g, h)) == compose(compose(f, g), h);
// true
这个特性就是结合律,符合结合律意味着不管你是把 g 和 h 分到一组,还是把 f 和 g 分到一组都不重要。
分享不易额,喜欢的话一定别忘了点💖!!!
只关注不点💖的都是耍流氓,只收藏也不点💖的也一样是耍流氓。