Scheme “两层”;“Y型”;组合器。这是常见的吗?这个有正式名称吗?
我一直在研究那些在def之前禁止使用并且没有可变单元格(noScheme “两层”;“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 =
set!
或setq
)的语言是如何提供递归的。我当然遇到了(著名?臭名昭著?)Y combinator和朋友,例如:
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_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
的行为符合预期,则通常会继续执行一步并停止。添加