关于柯里化

柯里化(Currying)把接受多个参数的函数变成单个参数的函数

比如我们有一个sum函数,接受三个参数

1
2
3
func sum(x, y, z int) int {
return x + y + z
}

改成接受单个变量

1
2
3
4
5
6
7
func curryingSum(x int) func(int) func(int) int {
return func(z int) func(int) int {
return func(y int) int {
return x + y + z
}
}
}

于是调用起来就从f(x, y, z)变为f(x)(y)(z)

1
2
sum(1, 2, 3)
curryingSum(1)(2)(3)

这样看起来性质很良好,只接受一个参数,返回一个输出。直觉告诉我们,接收单个变量的函数,比接收多个变量的函数有一些更加良好的性质。

但是go的curringSum函数要写返回值类型,看起来比较难受,用js来一版

1
2
3
4
5
6
7
const curryingAdd = function(x) {
return function(y) {
return function(z) {
return x + y + z
}
}
}

scheme版本的看起来也跟js差不多

1
2
3
4
5
(define (currySum f)
(lambda(x)
(labmda(y)
(lambda(z)
(f x y z)))))

对n个变量的Curry函数很容易实现

1
2
3
4
5
(define  (curry n f)  
(if (= n 1) f
(curry
(- n 1)
(lambda s (lambda (a) (apply f (append s (list a))))))))

js版本:

1
2
3
4
5
6
7
8
function curry(fn){
var args = Array.prototype.slice.call(arguments,1);
return function(){
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return fn.apply(null, finalArgs);
}
}

类似的,我们可以实现反柯里化,说明多变量函数和单变量单返回函数可以相互转换

1
2
3
4
5
(define ((uncurry f) . t)
(if (null? t)
f
(apply (uncurry (f (car t)))
(cdr t))))

那柯里化有什么用呢,可以用来实现多参数函数。(lambda演算是匿名的,并且只接收一个输入)在$\lambda$演算中,柯里化做了这样一个事情:

$$
\lambda x.(\lambda y.xy) = \lambda x.(\lambda y. xy)
$$

在手搓自制scheme中,我们已经实现了有一个接收一个输入、返回一个输出的函数,那么可以通过柯里化实现一个接收多参数的函数。

链接

柯里化
Lambda Calcus