Haskell无限递归
Haskell infinite recursion
以下函数计算斐波那契数列:
fib = 0 : 1 : (zipWith (+) fib (tail fib))
如果我们运行它,我们将得到一个无限列表,但是递归是如何工作的呢?如果函数不断调用自身,为什么它会在屏幕上打印数字?如果您能解释编译器如何管理调用,我将不胜感激。
一句话:懒惰。 Haskell 中的列表更像是一个生成器:它只会在其他东西需要时计算值。
例如 head [1 , 2+3]
不会执行加法,因为不需要。同理,如果我们递归地让ones = 1 : ones
,那么head ones = head (1 : ones) = 1
不需要计算所有的尾部。
你可以猜猜如果我们打印一对x
会发生什么,定义如下:
x = (n, fst x + 1)
上面我们使用(惰性)对而不是(惰性)列表,但推理是相同的。除非其他东西需要,否则不要评估任何东西。
我画了一张图,可能对你有帮助。
请注意 zipWtih op (x:xs) (y:xs) = (op x y):zipWith xs ys
,这就是 zipWtih
在列表中 "move" 的显示方式。它正在读取元素并输出总和:
这里有更详细的分步评估。 (虽然我会粘贴其中的副本,但内存中只有一份。)我将使用 ....
来表示我懒得写的东西。
fib = 0:1:zipWith (+) fib (tail fib)
= 0:1:zipWith (+) (0:1: .... ) (tail (0:1: .... )
= 0:1:(0+1:zipWith (+) (1:(0+1: .... )) ( 0+1:..... ))
= 0:1:1:zipWith (+) (1: ....) (......)
请注意,现在我们知道 zipWith (+) fib (tail fib) = 1:.....
。
= 0:1:1:zipWith (+) (1:1: ....) (1:......)
= 0:1:1:(1+1):zipWith (+) (1:(1+1): .....) ((1+1):....)
= 0:1:1:2:zipWith (+) (1:2: .....) (2:....)
我再快一点:
= 0:1:1:2:(1+2):zipWith (+) (2: .....) (....)
= 0:1:1:2:3 :zipWith (+) (2:3 .....) (3:....)
= 0:1:1:2:3:(2+3):zipWith (+) (3:(2+3):.....) ((2+3):.....)
= 0:1:1:2:3:5 :zipWith (+) (3:5:.....) (5:.....)
= 0:1:1:2:3:5:8 :zipWith (+) (5:8:....) (8:......)
= 0:1:1:2:3:5:8:13 :zipWith (+) (8:13:....) (13:......)
= 0:1:1:2:3:5:8:13:21:zipWith (+) (13:21....) (21:......)
在每个阶段,zipWith
函数的最后两个参数就像指向 fib
列表中比我们现在更靠上的(一个和两个位置)的指针。
以下函数计算斐波那契数列:
fib = 0 : 1 : (zipWith (+) fib (tail fib))
如果我们运行它,我们将得到一个无限列表,但是递归是如何工作的呢?如果函数不断调用自身,为什么它会在屏幕上打印数字?如果您能解释编译器如何管理调用,我将不胜感激。
一句话:懒惰。 Haskell 中的列表更像是一个生成器:它只会在其他东西需要时计算值。
例如 head [1 , 2+3]
不会执行加法,因为不需要。同理,如果我们递归地让ones = 1 : ones
,那么head ones = head (1 : ones) = 1
不需要计算所有的尾部。
你可以猜猜如果我们打印一对x
会发生什么,定义如下:
x = (n, fst x + 1)
上面我们使用(惰性)对而不是(惰性)列表,但推理是相同的。除非其他东西需要,否则不要评估任何东西。
我画了一张图,可能对你有帮助。
请注意 zipWtih op (x:xs) (y:xs) = (op x y):zipWith xs ys
,这就是 zipWtih
在列表中 "move" 的显示方式。它正在读取元素并输出总和:
这里有更详细的分步评估。 (虽然我会粘贴其中的副本,但内存中只有一份。)我将使用 ....
来表示我懒得写的东西。
fib = 0:1:zipWith (+) fib (tail fib)
= 0:1:zipWith (+) (0:1: .... ) (tail (0:1: .... )
= 0:1:(0+1:zipWith (+) (1:(0+1: .... )) ( 0+1:..... ))
= 0:1:1:zipWith (+) (1: ....) (......)
请注意,现在我们知道 zipWith (+) fib (tail fib) = 1:.....
。
= 0:1:1:zipWith (+) (1:1: ....) (1:......)
= 0:1:1:(1+1):zipWith (+) (1:(1+1): .....) ((1+1):....)
= 0:1:1:2:zipWith (+) (1:2: .....) (2:....)
我再快一点:
= 0:1:1:2:(1+2):zipWith (+) (2: .....) (....)
= 0:1:1:2:3 :zipWith (+) (2:3 .....) (3:....)
= 0:1:1:2:3:(2+3):zipWith (+) (3:(2+3):.....) ((2+3):.....)
= 0:1:1:2:3:5 :zipWith (+) (3:5:.....) (5:.....)
= 0:1:1:2:3:5:8 :zipWith (+) (5:8:....) (8:......)
= 0:1:1:2:3:5:8:13 :zipWith (+) (8:13:....) (13:......)
= 0:1:1:2:3:5:8:13:21:zipWith (+) (13:21....) (21:......)
在每个阶段,zipWith
函数的最后两个参数就像指向 fib
列表中比我们现在更靠上的(一个和两个位置)的指针。