Y组合器和尾部调用优化


20

F#中Y组合器的定义是

let rec y f x = f (y f) x

f希望将递归子问题的某些延续作为第一个论点。使用yf作为延续,我们可以看到f将应用于后续调用,因为我们可以开发

let y f x = f (y f) x = f (f (y f)) x = f (f (f (y f))) x etc...

问题是,先验的是,该方案无法使用任何尾部调用优化:的确,f中可能存在一些待处理的操作,在这种情况下,我们不能仅仅改变与f相关联的本地堆栈帧。

因此:

  • 一方面,使用Y组合器需要一个与函数本身截然不同的延续。
  • 在其他方面要应用TCO,我们希望f中没有待处理的操作,而仅调用f本身。

您知道这两者可以和解的任何方式吗?就像带累加器技巧的Y或CPS技巧的Y一样?还是有论据证明不可能做到这一点?


您是否已将rec键控添加到y的实现中?我想这需要从我读..
吉米·霍法

你有没有证明它不能优化尾声?我想你可能需要阅读的IL该功能,看看,我也不会感到惊讶,如果编译器是足够聪明拿出东西..
吉米·霍法

如果是直接的无限制递归,则不会:但是您可以重写它,以允许这样的事情,但前提是可以通过y调用重用堆栈帧。是的,可能需要查看IL,没有相关经验。
nicolas 2012年

5
我注册了一个帐户,并获得了50点评论。这个问题真的很有趣。我认为这完全取决于f。我们可以看到它y可能会因重击f而尾随(y f),但是正如您所说的,f可能还有一些待处理的操作。我想知道是否有一个单独的组合器对尾调用更友好是很有意思的。我想知道这个问题是否会在CS Stackexchange网站上得到更好的关注?
John Cartwright

Answers:


4

您知道这两者可以和解的任何方式吗?

不,恕我直言,这是有充分理由的。

Y组合器是一种理论构造,只需要使lambda演算完成就可以(请记住,lambda演算中没有循环,lambda也不具有可用于递归的名称)。

因此,Y组合器确实令人着迷。

但是:实际上没有人使用 Y组合器进行实际递归!(可能只是出于娱乐目的,以表明它确实有效。)

顾名思义,尾叫优化OTOH是一种优化。它不会增加语言的表示性,仅是因为我们关心它的实际考虑因素,例如堆栈空间和递归代码的性能。

所以您的问题是:是否存在减少beta的硬件支持?(Beta缩减是lambda表达式的缩减方式。)据我所知,没有一种函数语言将其源代码编译为lambda表达式的表示,该表示将在运行时进行beta缩减。


2
Y组合器就像重新打结一样,每次使用后都不会松开。大多数系统都简化了这一过程,并在元级别上打了个结,因此它永远都不需要取消。
Dan D.

1
对于最后一段,请考虑Haskell,其本质是使用图形归约法进行惰性评估。但是我最喜欢的是最优归约,它总是沿着Church-Rosser晶格移动,而归约到最小范式的次数最少。如出现在Asperti和Guerrini的“函数式编程语言的最佳实现”中。另请参阅BOHM 1.1
Dan D.

@丹 感谢您提供的链接,稍后我将在支持Postscript的浏览器中对其进行尝试。当然,我需要学习一些东西。但是,您确定编译的 haskell可以进行图形约简吗?我对此表示怀疑。
Ingo

1
实际上,它确实使用了图约简:“ GHC编译为无脊椎无标签G机(STG)。这是概念图约简机(即,如上所述执行图约简的虚拟机)。” From ...有关STG机器的更多信息,请参见Simon Peyton Jones的《在备用硬件上实现惰性功能语言:无脊椎无标签G机器》
Dan D.

@丹 在您链接的同一篇文章中,它进一步读到了GHC“在对该表示形式进行了一些优化之后,最终将其编译为实际的机器代码(可能通过使用GCC的C语言)。”
Ingo 2013年

0

我不确定这个答案,但这是我能想到的最好的答案。

y组合器本质上是惰性的,在严格的语言中,惰性必须通过额外的lambda手动添加。

let rec y f x = f (y f) x

您的定义似乎需要懒惰才能终止,否则该(y f)参数将永远无法完成评估,而必须评估是否f使用了它。懒惰上下文中的TOC更加复杂,并且的结果(y f)是重复的功能组合,而无需使用x。我不确定这种需求是否需要O(n)内存,其中n是递归的深度,但是我怀疑您是否可以使用类似的方法(切换到Haskell来实现相同类型的TOC,因为我实际上并不知道F#)

length acc []    = acc
length acc (a:b) = length (acc+1) b 

如果您还不了解它,那么Haskell foldl和之间的区别foldl'可能会使情况更加明朗。foldl是用急切的语言编写的。但是实际上,与其被TOC拒绝,还不如foldr因为累加器存储了无法部分评估的潜在巨大撞击。(这与为什么foldl和foldl'都不能在无限列表上起作用有关。)因此,在Haskell的最新版本中,foldl'添加了功能,每次函数重复出现时都强制对累加器进行求值,以确保不会产生巨大的重击。我确信http://www.haskell.org/haskellwiki/Foldr_Foldl_Foldl%27可以比我更好地解释这一点。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.