Web

浅谈函数式编程柯里化

Posted by Ivan on 2017-02-11

百度百科里对柯里化的定义是

在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。这个技术由 Christopher Strachey 以逻辑学家 Haskell Curry 命名的,尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的。

简直看不懂哦,Talk is Cheap,我们拿一个简单的haskell程序说明什么叫

自动柯里化

两数相加,返回结果:

1
fun add x y = x + y

这是一个典型的柯里化的版本,当我输入 (add 1) 2 时与add 1 2 的返回结果一模一样,都是3

1
2
Prelude> (add 1) 2
3

但是,换个角度上讲,我明明设置了两个“变量”,在haskell里却把它们的结果又封装成了一个函数,这就是haskell的强大特性:自动柯里化,haskell全部帮你自动处理了。

简单来说,柯里化指的是函数里只接收一个余下的参数,再调用原函数,返回出相应的结果。

而同样的,没有柯里化的普通版本却是这样的:

1
fun add(x, y) = x + y

这个版本里,只能通过add(1,2)进行相加操作。

柯里化实际上是将高阶函数进行降阶处理,没理解?接着看~

JavaScript的柯里化

JavaScript存在闭包的特性,能够很好的为我们将函数柯里化,但是很可惜JavaScript不支持自动柯里化,这意味着,我们要自己写柯里化的过程,不过还好,这能有效的让我们理解,到底什么是柯里化。

同样的,还是那个add的简单函数,用JavaScript实现如下:

1
2
3
4
5
6
7
function add(x, y){
if(y===undefined){
return (z) => x + z;
}else{
return x+y;
}
}

测试一下!通过执行add(1)(2),果然返回了3,是的,这是一个手动构造柯里化函数的过程。对于大多数函数,我们有通用的柯里化方法:

1
2
3
4
5
6
7
function curry(func) {
var fixedArgs = [].slice.call(arguments,1);
return function() {
args = fixedArgs.concat([].slice.call(arguments))
return func.apply(null, args);
};
}

也就是说,对于add函数来说,我们用一个函数生成了另一个范围更小的函数,一层接一层的解决问题。这符合函数式编程的思想,就是我们更关注函数函数之间的关系,而非状态本身,更在乎局部而非整体。

这个例子体现不出这种思想的精妙之处,那么我们再举一个例子🌰

如果,我们想让一个已知的数组每个值都做加1操作,传统的方法该怎么做?

1
2
let ar = [1,2,3];
ar.map((x)=> x+1);

对吧?那么我们下面可以用柯里化的思想把这个问题实现:

1
2
const add = ar => ar.map(x => x + 1);
add([1, 2, 3]);

首先我们在add里定义了一个函数,并且传入参数为arar进行Array.prototype.map()操作,对每一个数进行遍历,并且返回这个数字+1后的值给ar,这看起来挺玄乎的,但确实可行,我们只关心局部,请试图把add当做数学里中的函数去思考理解,或许会好懂的多。可是这只是方便了我们理解,那么到底有什么好处呢?

再举一个例子🌰

如果我们想写一个函数,用来对已知数组的已知量进行相加n的操作,怎么写?传统方法

1
2
3
4
5
let ar= [1,2,3];
function addToArray(ar,n){
return ar.map((x)=> x+n)
};
console.log(addToArray(ar,1));

但是,我们可以这样写:

1
2
3
const add = a => b => a+b;
const addToArray = (ar,n) => ar.map(add(n));
addToArray([2,3],1)

结果都一样,其实过程是不一样的,你可能真的憋不住了,到底有什么用呢?

柯里化的好处

就像上面的例子一样,你能看出不同吗?对,第一个的实质是新写了一个函数,对数组进行map操作,而第二个则是在直接在map里进行add操作,这很通用,一旦你写了很多类似于add的函数,就会方便起来,可以让我们方便的在map,reduce,filter里调用。

  • 能够进行延迟计算,就像add(1)(2)一样,1比2先传入,2就会被延迟计算,在特定的场景里,有一定的应用意义。
  • 当你发现你要调用一个函数,并且调用参数都是一样的情况下,这个参数就可以被柯里化,以便更好的完成任务。
  • 优雅的写法,允许你写出来的代码更干净、更有表达力。

虽然柯里化在日常应用不多,但仍然是一个不可不了解的好概念。

相关阅读

JavaScript柯里化

Currying and ES6 Arrow Functions

浅析 JavaScript 中的 函数 currying 柯里化

Currying - Part 6 of Functional Programming in JavaScript(需梯子)