0%

纯函数的好处

纯函数的概念

纯函数是这样一种函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用

比如 slicesplice,这两个函数的作用并无二致——但是注意,它们各自的方式却大不同,但不管怎么说作用还是一样的。我们说 slice 符合纯函数的定义是因为对相同的输入它保证能返回相同的输出。而 splice 却会嚼烂调用它的那个数组,然后再吐出来;这就会产生可观察到的副作用,即这个数组永久地改变了

在函数式编程中,我们讨厌这种会改变数据的笨函数。我们追求的是那种可靠的,每次都能返回同样结果的函数,而不是像 splice 这样每次调用后都把数据弄得一团糟的函数,这不是我们想要的。

来看看另一个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 不纯的
var minimum = 21;

var checkAge = function(age) {
return age >= minimum;
};


// 纯的
var checkAge = function(age) {
var minimum = 21;
return age >= minimum;
};

在不纯的版本中,checkAge 的结果将取决于 minimum 这个可变变量的值。换句话说,它取决于系统状态(system state);这一点令人沮丧,因为它引入了外部的环境,从而增加了认知负荷(cognitive load)。

追求纯的理由

可缓存性 (Cacheable)

首先,纯函数总能够根据输入来做缓存。实现缓存的一种典型方式是 memoize 技术:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var memoize = function(f) {
var cache = {};

return function() {
var arg_str = JSON.stringify(arguments);
cache[arg_str] = cache[arg_str] || f.apply(f, arguments);
return cache[arg_str];
};
};

var squareNumber = memoize(function(x){ return x*x; });

squareNumber(4);
//=> 16

squareNumber(4); // 从缓存中读取输入值为 4 的结果
//=> 16

squareNumber(5);
//=> 25

squareNumber(5); // 从缓存中读取输入值为 5 的结果
//=> 25

值得注意的一点是,可以通过延迟执行的方式把不纯的函数转换为纯函数:

1
2
3
var pureHttpCall = memoize(function(url, params){
return function() { return $.getJSON(url, params); }
});

可移植性/自文档化

纯函数是完全自给自足的,它需要的所有东西都能轻易获得。更易于观察和理解,没有偷偷摸摸的小动作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 不纯的
var signUp = function(attrs) {
var user = saveUser(attrs);
welcomeUser(user);
};

var saveUser = function(attrs) {
var user = Db.save(attrs);
...
};

var welcomeUser = function(user) {
Email(user, ...);
...
};

// 纯的
var signUp = function(Db, Email, attrs) {
return function() {
var user = saveUser(Db, attrs);
welcomeUser(Email, user);
};
};

var saveUser = function(Db, attrs) {
...
};

var welcomeUser = function(Email, user) {
...
};

这个例子表明,纯函数对于其依赖必须要诚实,这样我们就能知道它的目的。仅从纯函数版本的 signUp 的签名就可以看出,它将要用到 DbEmailattrs,这在最小程度上给了我们足够多的信息

可测试性

纯函数让测试更加容易。我们不需要伪造一个“真实的”支付网关,或者每一次测试之前都要配置、之后都要断言状态(assert the state)。只需简单地给函数一个输入,然后断言输出就好了。