Haskell 斐波那契函数如何展开?
我有一个非常简单的斐波那契函数:Haskell 斐波那契函数如何展开?,haskell,Haskell,我有一个非常简单的斐波那契函数: fibs = 1 : scanl (+) 1 fibs 但我不知道它将如何扩展。如您所见,这是一个递归函数 斐波那契序列号为(以1开头): 上面序列中的第二个数字(数字1),对我来说不清楚,为什么1不是2 请给我解释一下,fibs将如何扩展 更新 我试着展开如下: fibs = 1 : scanl (+) 1 fibs scanl (+) 1 [1](fibs) = 1 : ( case [1]
fibs = 1 : scanl (+) 1 fibs
但我不知道它将如何扩展。如您所见,这是一个递归函数
斐波那契序列号为(以1开头):
上面序列中的第二个数字(数字1),对我来说不清楚,为什么1
不是2
请给我解释一下,fibs
将如何扩展
更新
我试着展开如下:
fibs = 1 : scanl (+) 1 fibs
scanl (+) 1 [1](fibs) = 1 : (
case [1] of
[] -> []
x:xs -> scanl (+) (1+1) [1](fibs)
)
scanl (+) 2 [1](fibs) = 2 : (
case [1] of
[] -> []
x:xs -> scanl (+) (2+1) [1](fibs)
)
scanl (+) 3 [1](fibs) = 3 : (
case [1] of
[] -> []
x:xs -> scanl (+) (3+1) [1](fibs)
)
如您所见,尾部总是返回[1](fibs)
,这当然是错误的。最后一个表达式应为:
scanl (+) 3 [2](fibs) = 3 : (
case [2] of
[] -> []
x:xs -> scanl (+) (3+2) [3](fibs)
)
但我无法想象,它是如何工作的。扫描和折叠除了一件事之外是相似的。在“折叠”中,结果是最终值,但在“扫描”中,它是所有中间值直到最终值的列表。
当我们将第一个数字连接到列表的前面时,第一个斐波那契数字将是1。第二个斐波那契数也将是1(扫描的第二个参数),之后每个斐波那契数都是前一个数(扫描的运行总数)和前一个数的总和。我们有
fibs = 1 : scanl (+) 1 fibs
并且可以查找scanl
的定义
scanl :: (b -> a -> b) -> b -> [a] -> [b]
scanl = scanlGo
where
scanlGo :: (b -> a -> b) -> b -> [a] -> [b]
scanlGo f q ls = q : (case ls of
[] -> []
x:xs -> scanlGo f (f q x) xs)
在下面的分析中,我们将把scanlGo
看作scanl
λ> 2 : (scanl (+) 3 [2])
[2,3,5]
我们想看看haskell如何计算表达式
let fibs = 1 : scanl (+) 1 fibs
完全披露:由于表达是书面的,它没有得到
已评估(忽略可能的实现细节)
为什么呢?Haskell是懒惰的,表达式的计算直到
他们达到了“弱头范式”,也就是说,一个表达式
直到得到一个结果表达式,这是正常的
窗体,它是等待参数的数据构造函数或lambda
(即部分应用的功能)
因此绑定到名称fibs的值已经处于弱头法线中
表单,因为它是带有参数的数据构造函数
1 : scanl (+) 1 fibs
-- ^ this is the data constructor
所以要让haskell在这里评估任何东西,我们需要刺激它
有一点
让我们从
tail fibs
您不能在ghci中键入,因为它将尝试打印它,这将
进一步尝试在最终阶段对其进行评估。所以如果你想做实验
对于ghci,使用
let t = tail fibs
head t
那么如何计算表达式tail fibs
?大致上
像这样:
tail fibs
= tail (1 : scanl (+) 1 fibs)
= scanl (+) 1 fibs
= 1 : case fibs of { [] -> []; x:xs -> scanl (+) (1 + x) xs ; }
-- ^ data constructor
在这里,当我们到达一个数据构造函数时,它停止了<代码>头$尾小谎是
现在易于计算,无需计算任何case表达式
进一步的哈斯克尔现在知道,这个结果也有望被记住,
称为fibs
的表达式可以计算为
1 : 1 : case fibs of { [] -> []; x:xs -> scanl (+) (1 + x) xs ; }
让我们来询问尾部(尾部小谎)
:
在这里它又停止了。同样,结果将被记录下来,现在haskell
知道表达式fibs可计算为:
1 : 1 : 2 : case (tail fibs) of { [] -> []; x:xs -> scanl (+) (2+x) xs; }
现在是这样的,如果你要求更多的元素,还记得haskell吗
在达到标准形式或数据类型后,不会进一步计算表达式
构造函数或部分应用的函数,如果您不要求的话
旧答案:
基本上是这样
fibs = 1 : scanl (+) 1 fibs
fibs = 1 : 1 : scanl (+) 2 (tail fibs)
fibs = 1 : 1 : 2 : scanl (+) 3 (tail (tail fibs))
fibs = 1 : 1 : 2 : 3 : scanl (+) 5 (tail (tail (tail fibs)))
fibs = 1 : 1 : 2 : 3 : 5 : scanl (+) 8 (tail (tail (tail (tail fibs))))
在计算下一个列表元素时,scanl
get evaluation。所以我们直接得到下一个元素作为第二个参数传递给scanl的值。对于随后的元素,haskell存储计算,它将始终计算案例的第二个分支,因为我们在使用FIB时总是落后一个元素。这里,下一个斐波那契数计算为fqx
当然,它不会是
scanl的最后一个参数,但是有一些方法可以更直接地保持列表中的位置,正如我们已经计算过的那样。我使用了那些堆叠的tail
调用,以便于理解
附:对其中一条评论的补充回答:
1: tail fibs = scanl (+) 1 fibs
2: = scanl (+) 1 (1 : scanl (+) 1 fibs)
3: = 1 : scanl (+) 2 (scanl (+) 1 fibs)
-- with the equation from line 1 we can do
4: = 1 : scanl (+) 2 (tail fibs)
5: tail (tail fibs) = scanl (+) 2 (tail fibs)
6: = scanl (+) 2 (1 : scanl (+) 2 (tail fibs))
7: = 2 : scanl (+) 3 (scanl (+) 2 (tail fibs))
-- with the equation from line 5 we can do
8: = 2 : scanl (+) 3 (tail (tail fibs))
等等
PSS:在您尝试扩展它时,您设置了fibs=[1]
,这是错误的。它是
1:一些计算,由于懒散还没有完成。也许这会更清楚:
-- scanl f z [x1, x2, ...] == [z, z `f` x1, (z `f` x1) `f` x2, ...]
let fibs = 1 : scanl (+) 1 fibs
fibs
=> 1 : scanl (+) 1 fibs
=> 1 : scanl (+) 1 (1:...)
=> 1 : 1 : scanl (+) (1 + 1) (tail $ 1:1:...)
=> 1 : 1 : scanl (+) 2 (1:...)
=> 1 : 1 : 2 : scanl (+) (2 + 1) (tail $ 1:2:...)
=> 1 : 1 : 2 : scanl (+) 3 (2:...)
=> 1 : 1 : 2 : 3 : scanl (+) (3 + 2) (tail $ 2:3:...)
=> 1 : 1 : 2 : 3 : scanl (+) 5 (3:...)
=> 1 : 1 : 2 : 3 : 5 : scanl (+) (5 + 3) (tail $ 3:5:...)
=> 1 : 1 : 2 : 3 : 5 : scanl (+) 8 (5:...)
=> 1 : 1 : 2 : 3 : 5 : 8 : scanl (+) (8 + 5) (tail $ 5:8:...)
=> 1 : 1 : 2 : 3 : 5 : 8 : scanl (+) 13 (8:...)
=> 1 : 1 : 2 : 3 : 5 : 8 : 13 : scanl (+) (13 + 8) (tail $ 8:13:...)
=> 1 : 1 : 2 : 3 : 5 : 8 : 13 : scanl (+) 21 (13:...)
=> 1 : 1 : 2 : 3 : 5 : 8 : 13 : 21 : scanl (+) (21 + 13) (tail $ 13:21:...)
=> 1 : 1 : 2 : 3 : 5 : 8 : 13 : 21 : scanl (+) 34 (21:...)
一个帮助我认识到这个世界正在发生什么的视角
顺序是移除递归并手动构造它。到
为了更好地说明这一点,我将使用从
2和3:
这里所有的术语都不同,因此更容易区分
在他们之间
首先,生成前两个成员不需要递归:
λ> 2 : (scanl (+) 3 [])
[2,3]
也就是说,给出了第一个成员,第二个成员3
是
作为scanl
的初始累加器给出的值
λ> 2 : (scanl (+) 3 [2])
[2,3,5]
因此,当递归开始时,序列是[2,3]
。此时
累加器的值为3,是序列的第一项
是scanl
使用的下一个值
λ> 2 : (scanl (+) 3 [2])
[2,3,5]
因此,下一个输出是预期的3+2=5
在此之后,累加器的值为5,下一个值为
scanl
消耗的是3,因此下一个输出是8
λ> 2 : (scanl (+) 3 [2, 3])
[2,3,5,8]
希望这能对(某人)有所帮助。当我阅读《第一原理》中的Haskell编程时,有一个相同的表达式,我又花了几章时间才最终理解它是如何工作的。是的,我正在阅读这本书。顺便说一句,它不是递归函数,因为它根本不是函数。你可以称它为递归列表,或者(也许更清楚地说)递归定义的列表。
tail fibs=1:scanl(+)2(tail fibs)
什么是tail fibs
?它太难理解了,我需要一点时间来理解它。但是非常感谢你的帮助。还有一个关于(尾部小谎)
的问题。考虑下面的表达式<代码> FIB=1:1:2:SCALL(+)3(尾部(尾部FIB))< /代码>,它是如何得到值<代码> 2 < /代码>的?scan
函数在尾部调用fibs,它如何从const单元格中获取最后一个值?
λ> 2 : (scanl (+) 3 [2, 3])
[2,3,5,8]