部分应用与柯理化
在最近学习函数式编程的过程中,反复接触到的就是“柯理化”这个概念,特别数学范有没有?虽然看过多次,但是一直不是很好地理解它,恰逢今天在阅读《Scala 函数式编程》这本书的过程中加深了理解,便写个文章,总结一下。
柯理化
柯理化,英文叫“Currying”,命名源自逻辑学家 Haskell Curry 的名字。在数学和编程领域,柯理化用于将一个接收多个参数的函数转换为一系列只接收单个输入参数的函数。比如,将一个接收三个参数的函数 f
进行柯理化,会得到三个新的函数:
x = f(a, b, c) 变为:
h = g(a)
i = h(b)
x = i(c)
或者使用匿名函数按序调用的形式,则为:
x = g(a)(b)(c)
这样讲或许仍有点不好理解,我们用个数学函数的例子来分解。假如我们有函数 f(a, b, c) = a² + b - c
,并且有 a = 2
、b = 3
、c = 1
,则一般数学求解过程中,我们可以直接将 a、b、c 的值对应代入函数右侧式子,得到 2² + 3 - 1 = 6
,于是我们知道 f(2, 3, 1) = 6
。这个过程很直观很好理解,也很亲切对不对?
但是,假如我们要求每次只能代入函数的一个输入值,会是怎样的过程呢?
- 第一步,我们代入
a = 2
,我们将得到f(2, b, c) = 2² + b - c
,我们可以记g(b, c) = f(2, b, c) = 4 + b - c
; - 第二步,我们代入
b = 3
,我们得到g(3, c) = 4 + 3 - c
,我们可以记h(c) = g(3, c) = 7 - c
; - 最后一步,我们代入
c = 1
,我们得到h(1) = 7 - 1 = 6
。
上述的过程,向我们展示了我们是如何通过每次代入一个输入值而得到一个输入值数量减 1 的新函数。
- 第一步,
a
在f(a, b, c)
上的代入得到了g(b, c)
; - 第二步,
b
在g(b, c)
上的代入得到了h(c)
。
进一步,我们可以定义一组新的函数:
- 对应第一步,我们定义一个新函数
F(a)
,由于对a
的代入得到了g(b, c)
,于是F(a) = g(b, c)
,即F(a)
是一个输入为a
,输出为函数g(b, c)
的函数; - 对应第二步,我们定义一个新函数
G(b)
,由于对b
的代入得到了h(c)
,于是G(b) = h(c)
,即G(b)
是一个输入为b
,输出为函数h(c)
的函数; - 对应第三步,我们定义一个新函数
H(c)
,由于对c
的代入得到了最终式子,这里可以暂时记为H(c) => R
,R 表示实数集合。 - 以上三步,结合到一起,就有
f(a, b, c) = F(a)(b)(c)
。
这样循环通过每次只代入一个输入值最后得到一组每个函数都只有一个输入值的新函数的过程,就叫柯理化,而 F(a)
、G(b)
以及 H(c)
就是我们在柯理化过程中间得到的中间函数。
部分应用
从上面的分解过程可以看出,与我们习以为常的直接将 a, b, c
的值全部代入到式子中不同,我们在柯理化的过程中,每次只代入一个输入值,从而得到一个新的返回函数的函数,这种过程就叫部分应用。以下是部分应用的定义:
部分应用(partial application)这个名词,表示函数被应用的参数不是它所需要的完整的参数。
依然拗口,是不是?现在结合上面的例子,其实就好理解了,正常来说,我们要对 f(a, b, c)
求解,肯定是要应用 a
、b
、c
三个参数的,但是第一次只能应用 a
这个输入值,于是,我们还不能知道 f(a, b, c)
函数的值,但是我们得到了一个新的函数,这个新的函数将不再依赖 a
这个参数。
所以,柯理化的过程必然会有部分应用的身影,但是只是相关,并非等价,因为按照部分应用的定义,满足部分应用的过程不一定就是柯理化的过程,这个很好证伪,就不赘述了。
柯理化与函数闭包
在前面函数柯理化的过程中,我们将函数输入值代入之后的输出都是一个新的函数,对应到编程中就是返回函数的函数,而编程语言中柯理化实现的基础就依赖了闭包的功能,即返回的新函数隐含了对于外部函数的输入值的引用。这点也是将函数闭包和柯理化关联起来的比较有意思的一点。
参考资料
- 《Scala 函数式编程》—— Paul Chiusano, Runar Bjarnason 著
- Wikipedia: Currying
- Wikipedia: 闭包 (计算机科学)
- 题图来自 http://fruzenshtein.com/currying-partially-applied-function-scala/