[英]How does this haskell code work?
我是一名新生,我正在攻读计算机科学专业。 我们正在处理Haskell,虽然我理解Haskell的想法,但我似乎无法弄清楚我们应该看到的代码片段究竟是如何工作的:
module U1 where
double x = x + x
doubles (d:ds) = (double d):(doubles ds)
ds = doubles [1..]
我承认,对于知道发生了什么事的人来说似乎相当简单,但我无法绕过它。 如果我写“take 5 ds”,它显然会回复[2,4,6,8,10]。 我没有得到的,是为什么。
这是我的思路:我打电话给ds,然后寻找双打。 因为我也提交了值[1 ..],双精度(d:ds)应该表示d = 1且ds = [2 ..],对吗? 然后我将d加倍,返回2并将其放在列表的开头(数组?)。 然后它调用自身,将ds = [2 ..]转换为d = 2和ds = [3 ..],然后再次将d加倍并再次调用自身,依此类推,直到它可以返回5个值, [2,4,6,8,10]。
首先,我的理解是对的吗? 我的思绪中是否有任何严重的错误? 第二,因为它似乎将所有加倍的d保存到列表中以便稍后调用,那么该列表的名称是什么? 我在哪里完全定义它?
在此先感谢,希望你能帮助学生理解这个x)
我认为关于doubles
如何遍历无限列表的每个元素的递归/循环部分你是正确的。
现在关于
它似乎将所有加倍的d保存到列表中以便稍后调用,该列表的名称是什么? 我在哪里完全定义它?
这涉及在Haskell中称为Lazy Evaluation的功能。 该列表未预先计算并存储在任何位置。 相反,您可以想象列表是C ++中的一个函数对象,可以在需要时生成元素。 (您可能会看到的正常语言是表达式是按需评估的)。 所以,当你这样做
take 5 [1..]
[1..]
可以被视为一个函数对象,当与head
, take
等一起使用时会生成数字。所以,
take 5 [1..] == (1 : take 4 [2..])
这里[2..]
也是一个为你提供数字的“函数对象”。 同样,你可以拥有
take 5 [1..] == (1 : 2 : take 3 [3..]) == ... (1 : 2 : 3 : 4 : 5 : take 0 [6..])
现在,我们不需要关心[6..]
,因为对于任何xs
take 0 xs
xs
是[]
。 因此,我们可以拥有
take 5 [1..] == (1 : 2 : 3 : 4 : 5 : [])
无需存储任何“无限”列表,如[2..]
。 如果您想了解Lazy计算实际上是如何发生的,可以将它们视为函数对象/生成器。
你的思路看起来是正确的。 其中唯一的微小不准确在于使用表达式来描述计算,例如“它加倍2 然后调用自身......”。 在纯函数式编程语言中,例如Haskell,实际上没有固定的评估顺序。 具体来说,在
double 1 : double [2..]
在将列表的其余部分加倍之前是否发生加倍1是未指定的。 理论结果保证了秩序确实无关紧要,因为 - 大致 - 即使你以不同的顺序评估你的表达式,你也会得到相同的结果。 我建议您使用Lambda Bubble Pop网站查看此属性:您可以按不同顺序弹出气泡以模拟任何评估顺序。 无论你做什么,你都会得到相同的结果。
请注意,由于评估顺序无关紧要,Haskell编译器可以自由选择它认为最适合您的代码的任何评估顺序。 例如,让ds
定义为代码的最后一行,并考虑
take 5 (drop 5 ds)
这导致[12,14,16,18,20]
。 请注意,编译器不需要将前5个数字加倍,因为您正在删除它们,因此可以在完全计算之前删除它们(!!)。
如果你想进行实验,可以自己定义一个计算成本非常高的函数(比如,在递归定义后写下fibonacci
)。
fibonacci 0 = 0
fibonacci 1 = 1
fibonacci n = fibonacci (n-1) + fibonacci (n-2)
然后,定义
const5 n = 5
并计算
fibonacci 100
并观察实际需要多长时间。 然后,评估
const5 (fibonacci 100)
并且看到结果立即到达 - 参数甚至没有计算(!),因为不需要它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.