F# 函数式编程之 - 柯里化 currying

Author: 102419@gmail.com
Created at: 2020-12-02

即使没有专门学习过函数式的人也有可能听说过传说中的柯里化(currying), 这是一个比较 “出圈” 的概念,也是函数式编程的重要特性之一。

我会从最简单的情况开始讲述,刚开始你可能觉得无聊,但随着函数的演化,事情会开始变得有趣。

下面请看一个正常的 F# 函数:


    let add x y = x + y
    let three = add 1 2
    

上面这个 add 函数共有两个参数 x 和 y,如果调用函数时给齐参数,它返回计算结果,如上所示,第一行定义了一个函数 add, 第二行喂给它两个参数 1 和 2, 它返回计算值 3

所谓柯里化,就是当调用该函数时我们只给它一个参数,它就返回另一个函数,像这样:


    let add1 = add 1
    let three = add1 2
    

如上所示,我们给喂给 add 一个参数(整数 1),它自然无法完成计算,如果是普通的命令式编程,这样做会报错,但在函数式里,则会返回一个新的函数 add1, 函数 add 缺少的那一个参数就是 add1 的参数。

如果给 add1 喂参数 2,此时参数齐了,会返回计算结果 3

另外,还可以这样操作:


    let addX x = add x
    let add1 = addX 1
    let three = add1 2
    

在上面的代码中, addX 的类型是 int -> (int -> int), 意思是 addX 这个函数接受一个参数 int, 返回值是一个函数 (int -> int)

事情开始变得有趣了,还可以这样操作:


    let add x y = x + y
    let addX x = add x
    let addXY x y = addX x y
    let three = addXY 1 2
    

addXY 的类型(函数签名)是 int -> int -> int (正好与 add 的签名一模一样)

请仔细看,在上面的代码中,第三行(即 addXY 那行)里有这样的内容 addX x, 而在第二行也有相同的内容 addX x, 在上一篇文章(F# 函数式编程之 - 前所未见的 unit 类型)中说过 “函数式编程以数学里的函数概念为基础”,那么,也就是说:

已知 addXY x y = addX x y, 同时已知 addX x = add x, 可得 addXY x y = add x y

这正是 addXY 的签名与 add 的签名一模一样的原因,他们的计算结果也是一样的。

故事还没有结束,下面继续演化,是更有趣的事情

在普通的命令式编程里,加号 + 是一个运算符,但在 F# 里,加号 + 本质上是一个函数!而 x + y 其实是 (+) x y 的语法糖!

也就是说,当我们写下这句 let add x y = x + y, 事实上等于写了 let add x y = (+) x y

仔细观察,不难发现,我们自己定义的函数 add, 与 F# 自带的函数 (+) 是一模一样的。

本文到此结束,从最常见的情形出发,经过简单而曲拆的柯里化变换之后,我们又回到了最初的地方,但经过这一小段旅程,我相信你和我一样,再看同一行代码,会有一种透过表象看到本质的感觉。

←← →→