双层“Y型”组合器。这是常见的吗?这有官方名称吗?

时间:2013-04-28 00:27:34

标签: scheme lisp combinators y-combinator anonymous-recursion

我一直在研究禁止在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))))))

请注意,内部函数体在两者之间是相同的。

我的问题是:

  • 我的Y_letrec功能是否常用?
  • 它有一个完善的名字吗?

注意:上面的第一个链接指的是一个类似的功能(在“步骤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的位置。有关系吗?尽管有这些差异,这两个功能是否相同?

1 个答案:

答案 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?