Please enable Javascript to view the contents

柯里化与偏函数

 ·  ☕ 2 分钟

1. 什么是柯里化

根据维基百科词条定义,在计算机科学中,柯里化(Currying)是把接受多个参数的函数转变成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

英文版定义是一个两层的定语从句,翻译过来断句太长,上面的定义有些绕口。这里有几个关键点:

  • 多个参数转变成单一参数
  • 接受余下参数
  • 返回一个新函数

用表达式表示就是:

f(x, y, z, ...) => f(x)(y)(z)...

使用单一参数链式调用,替代多个参数调用。

2. 柯里化的用途

在函数式编程中,我们经常会写一些功能类似,参数不同的代码。由于没有继承或泛型,项目中会产生很多冗余的代码,难以维护。

柯里化能解决上面的问题。从定义可以看到,柯里化是用来生成函数。通过柯里化,能够简化函数的调用,编写更容易理解的代码。

在实现上,由于需要返回有状态的匿名函数,需要借助闭包实现,同时又涉及链式调用,需要借助递归函数实现。虽然在使用上很简单,但是编写一个 curry 函数并不简单。

3. 柯里化示例

下面是一段 JavaScript 的柯里化用例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
function curry(func) {
  return function curried(...args) {
    if (args.length >= func.length) {
      return func.apply(this, args);
    } else {
      return function(...args2) {
        return curried.apply(this, args.concat(args2));
      }
    }
  };
}
function sum(a, b, c) {
  return a + b + c;
}

let curriedSum = curry(sum);
alert(curriedSum(1)(2)(3))

在浏览器的 Console 中执行上面的代码,会弹框 6 ,执行 curriedSum(1, 2, 3) , curriedSum(1)(2,3) 也会得到同样的结果。

这段代码的逻辑是,比较 curried 实参和 sum 函数形参长度的大小,当实参少于形参时,递归调用继续增加实参数量,比如 (1) -> (1, 2)。直到实参为 (1, 2, 3) ,长度等于 sum 函数形参时,调用 sum 函数。

4. 与偏函数的区别

偏函数用表达式表示就是:

1
f(x, y, z, ...) => f(a, y, z, ...)

Python 提供了 functools.partial 函数,用于冻结参数。冻结部分参数,是偏函数的显著特征。看下面的示例:

1
2
3
4
5
6
7
8
9
from functools import partial

def join_string(*args):
    return '_'.join(args)

ok_join = partial(join_string, 'ok')
print(ok_join('a')) # ok_a
error_join = partial(join_string, 'error')
print(error_join('a')) # error_a

上面的示例,通过偏函数,也可以生成各种功能类似的函数。这在工程中也有着十分广泛的应用场景,比如有很多的功能模块需要打印日志,通过偏函数就可以为每一个功能模块生成一个 logger 。这样既能够解耦各个模块,还增加了代码的可读性,DevOpsLogger 、IstioLogger 、IamLogger 等。

5. 参考


微信公众号
作者
微信公众号