我一直在研究禁止 use-before-def 并且没有可变单元格(no set!
or setq
)的语言如何提供递归。我当然遇到了(著名的?臭名昭著的?)Y 组合器和朋友,例如:
- http://www.ece.uc.edu/~franco/C511/html/Scheme/ycomb.html
- http://okmij.org/ftp/Computation/fixed-point-combinators.html
- http://www.angelfire.com/tx4/cus/combinator/birds.html
- http://en.wikipedia.org/wiki/Fixed-point_combinator
当我以这种风格实现“letrec”语义时(也就是说,允许定义一个局部变量,使其可以是一个递归函数,在幕后它永远不会引用它自己的名字),组合器我最终写成这样:
Y_letrec = λf . (λx.x x) (λs . (λa . (f ((λx.x x) s)) a))
或者,分解出 U 组合子:
U = λx.x x
Y_letrec = λf . U (λs . (λa . (f (U s)) a))
将其读作: Y_letrec 是一个函数,它接受一个待递归函数f
。
f
必须是一个接受的单参数函数s
,其中s
是f
可以调用实现自递归的函数。f
预计将定义并返回执行“真实”操作的“内部”函数。该内部函数接受参数a
(或者在一般情况下是参数列表,但不能用传统符号表示)。调用 Y_letrec 的结果是调用的结果
f
,并且它被假定为一个“内部”函数,准备被调用。
我这样设置的原因是我可以直接使用待递归函数的解析树形式,而无需修改,只需在处理 letrec 时在转换过程中围绕它包裹一个额外的函数层。例如,如果原始代码是:
(letrec ((foo (lambda (a) (foo (cdr a))))))
那么转换后的形式将是:
(define foo (Y_letrec (lambda (foo) (lambda (a) (foo (cdr a))))))
请注意,两者之间的内部函数体是相同的。
我的问题是:
- 我的 Y_letrec 函数常用吗?
- 它有一个公认的名字吗?
注意:上面的第一个链接引用了与“应用顺序 Y 组合器”类似的函数(在“步骤 5”中),尽管我在找到该命名的权威来源时遇到了麻烦。
2013 年 4 月 28 日更新:
我意识到上面定义的 Y_letrec非常接近但不等同于维基百科中定义的 Z 组合子。根据 Wikipedia,Z 组合器和“按值调用 Y 组合器”是同一个东西,看起来这确实是更常被称为“应用顺序 Y 组合器”的东西。
所以,我上面所说的与通常写的应用顺序 Y 组合子不同,但几乎可以肯定它们是相关的。这是我进行比较的方法:
从...开始:
Y_letrec = λf . (λx.x x) (λs . (λa . (f ((λx.x x) s)) a))
应用内部 U:
Y_letrec = λf . (λx.x x) (λs . (λa . (f (s s)) a))
应用外部 U:
Y_letrec = λf . (λs . (λa . (f (s s)) a)) (λs . (λa . (f (s s)) a))
重命名以匹配 Wikipedia 对 Z 组合子的定义:
Y_letrec = λf . (λx . (λv . (f (x x)) v)) (λx . (λv . (f (x x)) v))
将此与维基百科的 Z 组合器进行比较:
Z = λf . (λx . f (λv . ((x x) v))) (λx . f (λv . ((x x) v)))
显着的区别在于函数f
的应用位置。有关系吗?尽管存在这种差异,这两个功能是否等效?