超级面板
文章目录
最新文章
最近更新
文章分类
标签列表
文章归档

函数式编程-纯函数

定义

实际上纯函数的定义更近似于数学上的函数的定义,数学上的函数定义如下:

设A,B是非空的数集,如果按照某种确定的对应关系f,使对于集合 A 中的任意一个数 x,在集合B中都有唯一确定的数 y 和它对应,那么就称 f:A->B 为从集合A 到集合B 的一个函数。

而纯函数的定义为:

纯函数定义为这样一种函数:对于相同的输入,其输出的结果永远是相同的,而且没有可见的副作用。

从定义上可以看出,纯函数和数学上的函数都有要求:对于任意相同输入,其输出结果都是 唯一确定 的,不存在一个输入对应于多个输出结果的情况。

纯函数和非纯函数很经常看到,比如数组的 slicesplice 函数,一个是纯函数,一个就是非纯函数:

slice 函数是纯函数,执行结果是可预计的、可靠的,每次输入相同的参数,执行操作的结果都一致:

var arr = [1, 2, 3, 4];

// 纯函数的示例:
arr.slice(0, 2);
// [1, 2]

arr.slice(0, 2);
// [1, 2]

splice 函数不是纯函数,输入相同的参数,每次操作的结果都不一致:

// 非纯函数的示例:
arr.splice(0, 2);
// [1, 2]

arr.splice(0, 2);
// [3, 4]

arr.splice(0, 2);
// []

副作用

前面提到纯函数不存在副作用,副作用的影响是这样的:

副作用表示在运算过程中,系统状态的改变或者与外部进行的可观察的交互。如果一个函数与外部的可变状态发生了交互,则认为函数是有副作用的。

可以看个例子:

var minAge = 21;

// 存在副作用的函数
var checkAge = age => age > minAge;

// 不存在副作用的函数
var checkAge = age => {
var minAge = 21;
return age > minAge;
}

从以上示例可以看到,对于同一个输入,第一个函数存在副作用,这个函数的行为不光取决于函数的参数,而且依赖外部变量 minAge ,如果外部修改了 minAge 的值,函数的执行结果可能发生改变,这种改变是不可预期的,而第二个函数不存在副作用,没有使用或修改任何的外部变量,因此可以保证它的输出结果始终是不会发生改变的。

对于一个系统而言,依赖外部系统环境的状态会造成额外的复杂度,而使用纯函数可以有效降低系统的复杂度,而且还有一些其他的特性:

纯函数的特性

纯函数具有很多可靠的特性:

可缓存性

因为纯函数的输出结果是确定的,只和输入有关,因此对于复杂运算可以将结果存储起来,下次直接使用。

如下定义了一个非常耗时的操作:

var complexFunc = (val) => {
// 耗时的操作
for(var i = 0; i < 100000000; i ++) {}
return val ++;
}

console.time('timer');
complexFunc(1);
console.timeEnd('timer');
// timer: 257ms

// 重复调用,函数重新运行,运行时间没有怎么发生变化。
console.time('timer');
complexFunc(1);
console.timeEnd('timer');
// timer: 227ms

这个操作执行较慢,每次都要花费大量时间,但是这是个纯函数,每次执行的结果是可预期的,因此,可以对其执行结果进行缓存:

//一个缓存函数结果的简单的实现
var memoize = function(f) {
var _cache = {};

return function() {
var argsStr = JSON.stringify(arguments);
_cache[argsStr] = _cache.hasOwnProperty(argsStr) ? _cache[argsStr] : f(arguments);
return _cache[argsStr];
}
}

var memoizedFunc = memoize(complexFunc);

然后再次执行这个函数:

// 第一次执行未缓存
console.time('timer');
memoizedFunc(1);
console.timeEnd('timer');
// timer: 250ms

// 此时运行结果已经被缓存,运行速度大幅增加。
console.time('timer');
memoizedFunc(1);
console.timeEnd('timer');
// timer: 0.0149ms

通过将执行结果缓存起来,虽然第一次调用的执行时间人就很长,但是当函数第二次被调用的时候,可以直接从缓存的数据中读取结果,函数的时间大大缩短。

可并行化

因为纯函数不需要访问共享数据,不会受外部状态影响而进行资源竞争,纯函数可以很容易的实行并行代码或者并行操作。

参考