简单理解柯里化函数

背景

Ant Desgin 用多了,想换种口味。所以最近改用   Material UI 开发页面。其中用到 Switch 组件时,在官方 demo 中发现一个箭头函数的写法:

1
2
3
const handleChange = name => event => {
setState({ ...state, [name]: event.target.checked });
};

作为一个「兼职」的前端开发,第一次看到双箭头函数的写法,自然要探个究竟。

定义

这种方式的写法究竟是什么呢?其实标题已经给出答案,官方定义其为柯里化函数:

在计算机科学中,柯里化(英語:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

用通俗一点的解释就是:一个多形参的函数在柯里化后变成每次调用时只处理一个参数。

举个栗子,假如给定一个函数 foo,它需要同时接收三个整型参数 a、b、c,并对它们求和。

那么函数 foo 的柯里化版本就是调用方式从原来的foo(a,b,c)变成了foo(a)(b)(c)。这是高阶函数的一种应用,也是函数式编程的重要思想。最后一次函数调用才真正返回结果值,中间的过程的调用返回值都是函数。

当然,可以用现在流行的箭头函数写法解决「回调地狱」,改造上面的表述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function foo(a, b, c) {
return a + b + c;
}

// 柯里化版本
function foo(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}

// 箭头函数的柯里化版本
const foo = a => b => c => a + b + c;

// 调用方式
const result = foo(1)(2)(3);

好处

假如 Switch 组件的变更函数不使用柯里化方式,调用方式将会是什么样呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 不需要传入 name 时
const handleChange(event)=> {

};

<Switch onChange={handleChange}/>

// =================================

// 需要传入 name
const handleChange(event, name)=> {

};

// ERROR:这样组件渲染时会直接调用 handleChange
<Switch onChange={handleChange('checkedA')}/>

// FIXED: 需要使用箭头函数处理函数绑定
<Switch onChange={(e)=>{handleChange(e, 'checkedA')}}/>

通过上面的例子可以看到,假如不需要传入多余的参数,可以直接绑定函数名。如果需要传入自定义参数,那么非柯里化的函数在绑定时无法直接写成函数调用的形式,必须通过匿名的箭头函数包裹我们的调用函数。所以柯里化后的写法上可以直接使用直觉上的函数绑定形式。

加料区

另外这个代码中可以关注到的一个小知识点是: 变量作为字符串传入时,可以通过括号包裹后变成 JSON 字符串的 key 值,否则直接填入变量名的话,key 就是其字面值了。

1
2
3
4
5
6
7
function handleChange(name) {
console.log({ name: null });
console.log({ [name]: null });
}

handleChange("checkedA");
handleChange("checkedB");

参考资料

https://stackoverflow.com/questions/36314/what-is-currying
https://zh.wikipedia.org/zh/%E6%9F%AF%E9%87%8C%E5%8C%96
https://medium.com/javascript-scene/curry-and-function-composition-2c208d774983