在chapter about function in the Oz tutorial中,它说:
类似于懒惰的函数式语言 Oz允许某些形式的 尾递归优化 没有发现某些严格的功能 语言包括标准ML, 方案,并发功能 语言Erlang。但是,标准 Oz中的函数定义不是 懒惰。
然后继续显示以下Oz中尾递归的函数:
fun {Map Xs F}
case Xs
of nil then nil
[] X|Xr then {F X}|{Map Xr F}
end
end
这样做,它将空列表映射到空列表和非空列表,将函数F
应用到其头部,然后将其前置于调用{{1在尾巴上。在其他语言中,这不是尾递归,因为最后一个操作是前置操作,而不是对Map
的递归调用。
所以我的问题是:如果“Oz中的标准函数定义不是懒惰的”,那么Oz做什么,像Scheme或Erlang这样的语言不能(或不会?)能够执行尾递归优化这个功能?确切地说,什么时候函数在Oz中是尾递归的?
答案 0 :(得分:4)
这称为Tail Recursion Modulo Cons
。基本上,在递归调用之后直接将添加到列表中将追加直接添加到之前递归调用(从而将列表构建为"副作用"纯函数"循环")。这是尾递归的推广,它不仅适用于cons
列表,而且适用于任何具有常量运算的数据构造函数。
由Daniel P. Friedman和David S. Wise在Technical Report TR19: Unwinding Structured Recursions into Iterations于1974年首次描述(但未命名)为LISP编译技术,并于1980年由David HD Warren正式命名和介绍。编写有史以来第一个Prolog编译器的上下文。
然而,关于Oz的有趣之处在于,TRMC既不是语言特性也不是显式编译器优化,它只是语言执行语义的副作用。具体来说,Oz是一种声明式并发约束语言,这意味着每个变量都是一个数据流变量(或者#34;一切都是一个承诺",包括每个存储位置)。由于一切都是承诺,我们可以建模从函数返回 first 将返回值设置为promise,然后稍后实现它。 彼得·范罗伊是本书Concepts, Techniques, and Models of Computer Programming by Peter Van Roy and Seif Haridi的合着者之一,同时也是奥兹的设计师之一,也是其实施者之一,他解释了TRMC如何在Lambda the Ultimate的评论主题中发挥作用:{{3 }}:上面的错误Scheme代码示例在直接转换为Oz语法时变成了良好的尾递归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
最初是未绑定的。这不仅仅是一个聪明的伎俩;它是深刻的,因为它允许声明性并发和声明性多代理系统。
答案 1 :(得分:3)
我对懒惰的函数式语言并不太熟悉,但是如果你在你的问题中考虑函数Map,如果允许堆中的临时不完整的值,则很容易转换为尾递归实现(静音为更完整一次调用一次)。
我必须假设他们正在谈论奥兹的这种转变。 Lispers曾经手工完成这项优化 - 所有值都是可变的,在这种情况下会使用一个名为setcdr
的函数 - 但你必须知道你在做什么。计算机并不总是有几千兆字节的内存。手工做到这一点是合理的,可以说它不再是。
回到你的问题,其他现代语言不会自动执行,可能是因为在构建它时可以观察到不完整的值,这必须是Oz找到解决方案的原因。与其他解释它的语言相比,Oz还有哪些不同之处?