Scheme “两层”;“Y型”;组合器。这是常见的吗?这个有正式名称吗?

Scheme “两层”;“Y型”;组合器。这是常见的吗?这个有正式名称吗?,scheme,lisp,combinators,y-combinator,anonymous-recursion,Scheme,Lisp,Combinators,Y Combinator,Anonymous Recursion,我一直在研究那些在def之前禁止使用并且没有可变单元格(noset!或setq)的语言是如何提供递归的。我当然遇到了(著名?臭名昭著?)Y combinator和朋友,例如: 当我以这种风格实现“letrec”语义时(也就是说,允许定义一个局部变量,使其成为递归函数,在这种情况下,它永远不会引用自己的名称),我最后编写的组合器如下所示: Y_letrec = λf . (λx.x x) (λs . (λa . (f ((λx.x x) s)) a)) 或者,分解出U组合子: U =

我一直在研究那些在def之前禁止使用并且没有可变单元格(no
set!
setq
)的语言是如何提供递归的。我当然遇到了(著名?臭名昭著?)Y 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 . (λx . (λv . (f (x x)) v)) (λx . (λv . (f (x x)) v))
Z        = λf . (λx . f (λv . ((x x) v))) (λx . f (λv . ((x x) v)))
可以这样理解: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与Wikipedia中定义的Z组合词非常接近,但并不完全相同。根据维基百科,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组合器的定义:

U = λx.x x
Y_letrec = λf . U (λs . (λa . (f (U s)) a))
Y_letrec = λf . (λx . (λv . (f (x x)) v)) (λx . (λv . (f (x x)) v))
Z        = λf . (λx . f (λv . ((x x) v))) (λx . f (λv . ((x x) v)))
将其与维基百科的Z combinator进行比较:

U = λx.x x
Y_letrec = λf . U (λs . (λa . (f (U s)) a))
Y_letrec = λf . (λx . (λv . (f (x x)) v)) (λx . (λv . (f (x x)) v))
Z        = λf . (λx . f (λv . ((x x) v))) (λx . f (λv . ((x x) v)))

显著的区别在于应用函数
f
的位置。这有关系吗?尽管存在这种差异,这两个函数是否等价?

是的,它是一个适用的Y阶组合符。在里面使用U完全可以,我也这样做了(参见)。无论使用U来缩短代码是否有名称,我不这么认为。这只是一个lambda术语的应用,是的,它也使它更清晰

它的名称是eta转换,在代码中用于延迟应用程序顺序下的求值,在函数应用程序之前必须知道参数的值

随着通过和通过应用U,并在代码上执行eta缩减(
(λa.(f(s))a)
==>
f(s)
),它成为熟悉的正常顺序Y组合符-即在正常顺序求值下工作,在函数应用之前不需要参数值,最终可能不需要它们(或其中一些):

Y = λf . (λs.f (s s)) (λs.f (s s))
顺便说一句,延迟可以以稍微不同的方式应用

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:虽然被定义的语言可能没有赋值和指针相等这样有趣的东西,但我们定义它的语言肯定会有这些东西,因为我们需要它们来提高效率。至少,它的实施将把它们隐藏起来


另请参见:,尤其是。

我记得,应用程序顺序与正常顺序相比较。在应用程序顺序语言(如Scheme)中,参数会在函数看到它们之前立即求值。这使得定义Y组合子变得复杂。与传统的lambda演算一样,在正常顺序下,参数是传递的,只有在没有其他选项时才进行计算。Y-组合子在正常顺序下更简单,例如p。34.非常感谢。是否有理由选择一种形式而不是另一种形式?经过大量的探索和修补,我最终来到了我的矿场,当我发现它并不完全相同时,我感到惊讶。在还原顺序上有一个小的差异。你的版本实际上更好,我认为,它确保减少停止;如果
f
的行为符合预期,则通常会继续执行一步并停止。添加