弄清楚 Thunk 函数的前提下先看两个定义,什么是求值策略?
求值策略
求值策略即编程语言的函数在进行参数传递时,函数的参数到底应该什么时候进行计算求值比较好;这里也是一个争论点,观点一是“传值调用(call by value)”,观点二是“传名调用(call by name)”;那么这两种观点有什么区别呢?
- 传值调用
例如:
1 | var x = 1; |
在这个函数上如果用传值调用(call by value)的话,即在进入函数体之前就计算 x + 5 的值(等于 6),再将已经求出的这个值传入函数 f;c 语言就是这种策略。
1 | f(x + 5) |
- 传名调用
还是前面的例子,如果用传名调用(call by name)的话,即直接将表达式 x + 5 传入函数体,只在函数体内部用到它的时候进行求值; Hskell 语言就是采用的这种策略。
1 | f(x + 5) |
通过上面两个例子的说明,传值调用和传名调用哪一种方法比较好,回答是各有利弊。这里的传值调用比较简单,但是对参数求值的时候,实际上还没用到这个参数,有可能造成性能损失。
1 | function f(a, b) { |
这里的示例中就是在函数 f 的参数中传入了两个参数并进行了大量计算,而函数体内并没有用到第一个进行大量计算的参数值,实际上对这个参数的求值就是没有必要的;因此,有一些人更倾向于“传名调用”,即在执行的时候求值。
Thunk 函数的含义
在编译器中的“传名调用”实现时,往往是将参数放到一个临时函数中,再将这个临时函数传入函数体。而这个临时函数就叫做 Thunk 函数。
1 | function f(m) { |
在这里,函数 f 中的参数 x + 5 被一个函数替换了,凡是用到原参数的地方,对 Thunk 函数求值即可。
**这就是 Thunk 函数的定义,它是“传名调用”的一种实现策略,用来替换某个表达式。
JavaScript 语言的 Thunk 函数
JavaScript 语言是传值调用,它的 Thunk 函数含义有所不同。在 JavaScript 语言中, Thunk 函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数作为参数。
1 | // 正常版本的 readFile(多参数) |
这里把 fs 模块的 readFile 方法从多个参数转换成单个参数的函数后,这个单参数的版本就是 Thunk 函数。
最后,只要参数有回调函数,就能写成 Thunk 函数的形式,这里给出一个 Thunk 函数转换器做记录(来自阮老师):
1 | var Thunk = function (fn) { |
总结
Thunk 函数其实就是“传名调用”的一种实现策略,将参数放到一个临时函数并将临时函数传入函数体进行执行。