Oz 中的尾递归优化

Posted

技术标签:

【中文标题】Oz 中的尾递归优化【英文标题】:Tail-recursion optimization in Oz 【发布时间】:2010-12-03 13:34:13 【问题描述】:

在chapter about function in the Oz tutorial 中,它说:

类似于惰性函数式语言 Oz 允许某些形式的 尾递归优化是 在某些严格的函数中找不到 包括标准 ML 在内的语言, 方案和并发功能 语言二郎。然而,标准 Oz 中的函数定义不是 懒惰。

接着在Oz中显示以下尾递归函数:

fun Map Xs F
   case Xs
   of nil then nil
   [] X|Xr then F X|Map Xr F
   end 
end 

它的作用是,它将空列表映射到空列表和非空列表,将函数 F 应用到其头部,然后将其添加到调用 Map 的结果之前尾巴。在其他语言中,这不是尾递归,因为最后一个操作是前置操作,而不是对 Map 的递归调用。

所以我的问题是:如果“Oz 中的标准函数定义不是惰性的”,那么 Oz 会做什么,像 Scheme 或 Erlang 这样的语言不能(或不会?)能够执行尾递归优化这个功能? Oz 中的尾递归函数究竟是什么时候?

【问题讨论】:

【参考方案1】:

这称为Tail Recursion Modulo Cons。基本上,prepending 到列表直接 after 递归调用与 appending 到列表直接 before 相同递归调用(因此将列表构建为纯功能“循环”的“副作用”)。这是尾递归的概括,不仅适用于cons 列表,还适用于任何具有常量操作的数据构造函数。

1974 年,Daniel P. Friedman 和 David S. Wise 在 Technical Report TR19: Unwinding Structured Recursions into Iterations 中首次将其描述(但未命名)为 LISP 编译技术,并于 1980 年由 David HD Warren 在编写有史以来第一个 Prolog 编译器。

不过,关于 Oz 的有趣之处在于,TRMC 既不是语言特性,也不是显式编译器优化,它只是语言执行语义的副作用。具体来说,Oz 是一种声明式并发约束语言,这意味着每个变量都是数据流变量(或“一切都是承诺”,包括每个存储位置)。由于一切都是一个承诺,我们可以将函数的返回建模为首先将返回值设置为一个承诺,然后稍后实现它。

Peter van Roy,Concepts, Techniques, and Models of Computer Programming by Peter Van Roy and Seif Haridi 这本书的合著者,也是 Oz 的设计者之一,也是它的实现者之一,他在 Lambda the Ultimate 的评论线程中解释了 TRMC 的工作原理:Tail-recursive map and declarative agents

当直接翻译成 Oz 语法时,上面的不良 Scheme 代码示例会变成良好的尾递归 Oz 代码。这给出了:

fun Map F Xs
   if Xs==nil then nil
   else F Xs.1|Map F Xs.2 end
end

这是因为 Oz 具有单赋值变量。为了理解执行,我们将这个例子翻译成 Oz 内核语言(为了清楚起见,我只给出了部分翻译):

proc Map F Xs Ys
   if Xs==nil then Ys=nil
   else local Y Yr in
      Ys=Y|Yr
      F Xs.1 Y
      Map F Xs.2 Yr
   end end
end

也就是说,Map 是尾递归的,因为 Yr 最初是未绑定的。这不仅仅是一个聪明的把戏;它很深刻,因为它允许声明式并发和声明式多代理系统。

【讨论】:

【参考方案2】:

我对惰性函数语言不太熟悉,但是如果您考虑问题中的函数 Map,如果允许堆中暂时不完整的值(静音为更完整的值),则很容易转换为尾递归实现一次重视一个电话)。

我不得不假设他们正在谈论 Oz 中的这种转变。 Lispers 过去常常手动进行这种优化——所有值都是可变的,在这种情况下,将使用一个名为setcdr 的函数——但你必须知道你在做什么。计算机并不总是有千兆字节的内存。手动执行此操作是合理的,可以说不再是。

回到您的问题,其他现代语言可能不会自动执行此操作,可能是因为在构建时可以观察到不完整的值,而这一定是 Oz 找到的解决方案。与其他可以解释它的语言相比,Oz 中还有哪些其他差异?

【讨论】:

我对 oz 的了解并不多。据我所知,它与 lisp 并没有太大的不同。在过去的几天里,我只是玩了一下它,偶然发现它优化了递归,这是我没想到的。 我是通过阅读 Peter Van Roy 的 Concepts, Techniques, and Models of Computer Programming 才知道 Oz,但实际上它确实具有不完整的值——它们广泛用于并发编程,因为读取不完整的值会导致当前线程阻塞。所以 Pascal 的猜测可能就是它的工作原理。 (这里是书的链接:info.ucl.ac.be/~pvr/book.html他以前网上有个草稿版,现在删掉了。:() 由于 Oz 也是一种逻辑语言,因此它可能具有逻辑变量的概念,在这种情况下,它会像 prolog 等其他逻辑语言一样是尾递归的。 查看这里了解它的工作原理:lambda-the-ultimate.org/node/2273#comment-40235

以上是关于Oz 中的尾递归优化的主要内容,如果未能解决你的问题,请参考以下文章

一个很Cool的Idear->Python的尾递归优化

XSLT 2.0 中的尾递归函数不起作用

G++ 尾递归优化失败

加载大量项目时的尾递归

方程 Tn=n∑k=1 =k 的尾递归函数

如何在 Scala 中使用 Stream.cons 编写不泄漏的尾递归函数?