- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我的理解(可能不正确或不完整)是惰性求值可以提供尾递归的所有优点,并且做得更好。
如果是这样,是否意味着在惰性求值的情况下不需要尾递归?
更新
具体来说,让我们看以下示例:
(define (foo f a)
(if (number? a)
(* a a)
(lazy-map foo a)))
这个函数可以很容易地转换为尾递归函数。但是,如果是这样,我们将失去惰性求值的优势。
实际上,当输入是一个相当大的列表(或无限)时,这个非尾递归函数是否需要消耗很多堆栈?我不这么认为。那么,是否有充分的理由使用尾递归而不是惰性求值?
最佳答案
TL;DR 在惰性语言中,“尾递归”定义函数通常没什么用。即使在这些情况下,您也可能会出现堆栈溢出(与具有适当尾部调用优化的严格语言相反)。
通过使用函数参数的惰性求值(按名称调用),您将失去用递归表达简单迭代(使用常量空间)的能力。
例如,让我们比较长度函数的两个版本。首先我们看一下非尾递归函数来比较惰性和非惰性:
length [] = 0
length (head:tail) = length tail + 1
严格评估长度[1,2,3]
:
length [1, 2, 3] ->
length (1:[2, 3]) = length [2, 3] + 1 ->
length (2:[3]) + 1 = (length [3] + 1) + 1 ->
(length (3:[]) + 1) + 1 = ((length [] + 1) + 1) + 1 ->
((length [] + 1) + 1) + 1 = ((0 + 1) + 1) + 1 ->
(1 + 1) + 1 ->
2 + 1
3
惰性评估:
length [1, 2, 3] ->
length (1:[2, 3]) = length [2, 3] + 1 ->
此时需要减少 +
并且需要评估两个参数,第一个是 length [2, 3]
,因此它会像以前一样继续,但是在一个少一个参数的列表上。堆栈空间用于评估+
(如果我们正在考虑简单的实现)。
因此,两个版本都使用堆栈,但对于懒惰的版本来说,+
是这里的“递归”函数,而不是 length
。
尾递归变体(使用累加器):
length [] a = a
length (head:tail) a = length tail (a + 1)
利用尾调用优化的严格评估步骤:
length [1, 2, 3] 0 ->
length (1:[2, 3]) 0 = length [2, 3] (0 + 1) ->
length (2:[3]) 1 = length [3] (1 + 1) ->
length (3:[]) 2 = length [] 2 + 1 ->
length [] 3 = 3
这在堆栈上使用常量空间,在堆上没有空间
惰性评估:
length [1, 2, 3] 0 ->
length (1:[2, 3]) 0 = length [2, 3] (0 + 1) ->
length (2:[3]) (0 + 1) = length [3] ((0 + 1) + 1) ->
length (3:[]) ((0 + 1) + 1) = length [] ((0 + 1) + 1) + 1 ->
length [] (0 + 1) + 1) + 1 = ((0 + 1) + 1) + 1 ->
magic -> 3
这在堆栈上使用恒定空间,直到“神奇”部分,但在堆上(通常)建立延迟计算(添加)。标记为“magic”的部分是所有的总和发生的地方,在简单的实现中它使用堆栈。 (请注意,在这种情况和类似情况下,优化评估器实际上可能在评估过程中进行加法,然后只返回 3,您无法真正分辨出差异,但不使用堆栈。它可以使用其他技巧来防止堆栈溢出如 CPS)。
摘要:
惰性计算函数在其直接实现中最终使用堆栈,无论它们是否被编写为尾递归。
但是,您需要对编译器应用各种技巧来优化堆栈使用。不过,它们并不像严格语言中的尾部调用优化那么简单。
更好的选择是惯用地使用这些语言,并利用各种高阶函数,从而显着减少对递归函数定义的需求(并且在某种程度上也出于这个原因而使用惰性语言)。
关于haskell - 在惰性求值的情况下,什么时候需要尾递归?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34685552/
我是 Java 新手,这是我的代码, if( a.name == b.name && a.displayname == b.displayname && a.linknam
在下面的场景中,我有一个 bool 值。根据结果,我调用完全相同的函数,唯一的区别是参数的数量。 var myBoolean = ... if (myBoolean) { retrieve
我是一名研究 C++ 的 C 开发人员: 我是否正确理解如果我抛出异常然后堆栈将展开直到找到第一个异常处理程序?是否可以在不展开的情况下在任何 throw 上打开调试器(即不离开声明它的范围或任何更高
在修复庞大代码库中的错误时,我观察到一个奇怪的情况,其中引用的动态类型从原始 Derived 类型更改为 Base 类型!我提供了最少的代码来解释问题: struct Base { // some
我正在尝试用 C# 扩展给定的代码,但由于缺乏编程经验,我有点陷入困境。 使用 Visual Studio 社区,我尝试通过控制台读出 CPU 核心温度。该代码使用开关/外壳来查找传感器的特定名称(即
这可能是一个哲学问题。 假设您正在向页面发出 AJAX 请求(这是使用 Prototype): new Ajax.Request('target.asp', { method:"post", pa
我有以下 HTML 代码,我无法在所有浏览器中正常工作: 我试图在移动到
我对 Swift 很陌生。我如何从 addPin 函数中检索注释并能够在我的 addLocation 操作 (buttonPressed) 中使用它。我正在尝试使用压力触摸在 map 上添加图钉,在两
我设置了一个详细 View ,我是否有几个 Nib 文件根据在 Root View Controller 的表中选择的项目来加载。 我发现,对于 Nibs 的类,永远不会调用 viewDidUnloa
我需要动态访问 json 文件并使用以下代码。在本例中,“bpicsel”和“temp”是变量。最终结果类似于“data[0].extit1” var title="data["+bpicsel+"]
我需要使用第三方 WCF 服务。我已经在我的证书存储中配置了所需的证书,但是在调用 WCF 服务时出现以下异常。 向 https://XXXX.com/AHSharedServices/Custome
在几个 SO 答案(1、2)中,建议如果存在冲突则不应触发 INSERT 触发器,ON CONFLICT DO NOTHING 在触发语句中。也许我理解错了,但在我的实验中似乎并非如此。 这是我的 S
如果进行修改,则会给出org.hibernate.NonUniqueObjectException。在我的 BidderBO 类(class)中 @Override @Transactional(pr
我使用 indexOf() 方法来精细地查找数组中的对象。 直到此刻我查了一些资料,发现代码应该无法正常工作。 我在reducer中尝试了上面的代码,它成功了 let tmp = state.find
假设我有以下表格: CREATE TABLE Game ( GameID INT UNSIGNED NOT NULL, GameType TINYINT UNSIGNED NOT NU
代码: Alamofire.request(URL(string: imageUrl)!).downloadProgress(closure: { (progress) in
我是一名优秀的程序员,十分优秀!