我一直在研究禁止在def之前使用并且没有可变单元格(没有set!
或setq
)的语言如何提供递归。我当然遇到了(着名的?臭名昭着的?)Y组合者和朋友,例如:
当我在这种风格中实现“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))))))
请注意,内部函数体在两者之间是相同的。
我的问题是:
注意:上面的第一个链接指的是一个类似的功能(在“步骤5”中)作为“applicative-order Y combinator”,虽然我很难找到该命名的权威来源。
更新28-apr-2013:
我意识到上面定义的Y_letrec 非常接近,但与维基百科中定义的Z组合器不完全相同。根据维基百科,Z组合子和“按值调用的Y组合子”是相同的,看起来确实可能更常被称为“应用顺序Y组合子”。
所以,我上面所说的 not 与通常编写的应用顺序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))
重命名以匹配维基百科对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
的位置。有关系吗?尽管有这些差异,这两个功能是否相同?
答案 0 :(得分:5)
是的,它是一个应用订单Y组合子。在里面使用U是完全可以的,我也是这样做的(参见fixed point combinator in lisp)。使用U来缩短代码是否有名字,我不这么认为。它只是一个lambda术语的应用,是的,它也使IMO更加清晰。
有什么名称,即eta转换,在您的代码中用于延迟评估的应用顺序,其中在功能应用之前必须知道参数的值。
在你的代码((λa.(f (s s)) a)
==> f (s s)
)上执行U通过和通过以及eta-reduction,它成为熟悉的正常顺序Y组合子 - 即在正常情况下正常工作-order evaluation,其中在功能应用之前不需要参数值,最终可能不需要它们(或其中一些):
Y = λf . (λs.f (s s)) (λs.f (s s))
BTW延迟可以稍微不同的方式应用,
Y_ = λf . (λx.x x) (λs.f (λa.(s s) a))
也适用于应用订单评估规则。
有什么区别?让我们比较减少序列。你的版本,
Y_ = λf . (λx . (λv . (f (x x)) v)) (λx . (λv . (f (x x)) v))
((Y_ f) a) =
= ((λx . (λv . (f (x x)) v)) (λx . (λv . (f (x x)) v))) a
= (λv . (f (x x)) v) a { x := (λx . (λv . (f (x x)) v)) }
= (f (x x)) a
= | ; here (f (x x)) application must be evaluated, so
| ; the value of (x x) is first determined
| (x x)
| = ((λx . (λv . (f (x x)) v)) (λx . (λv . (f (x x)) v)))
| = (λv . (f (x x)) v) { x := (λx . (λv . (f (x x)) v)) }
并输入此处f
。所以在这里,表现良好的函数f
接收它的第一个参数,并且它应该不对它做任何事情。所以也许两者完全相同。
但实际上,lambda表达式定义的细节在实际实现时并不重要,因为真正的实现语言将有指针,我们只是操纵它们以正确指向包含表达式主体,而不是它的副本。毕竟,使用铅笔和纸进行Lambda演算,作为文本复制和替换。 lambda演算中的Y组合子仅模拟递归。真正的递归是真的自引用; 接收副本等于自我,通过自我应用(无论多么聪明)。
TL; DR:虽然定义的语言可能没有分配和指针相等等有趣的东西,但我们定义它的语言肯定会有这些,因为我们需要它们来提高效率。至少,它的实现将拥有它们。
另见:fixed point combinator in lisp,尤其是In Scheme, how do you use lambda to create a recursive function?