我是 Haskell 的新手,我有一个问题
h x = x : (h x)
g xs = [head xs, head xs - 1]
g(h 2)
假设语义是 call-by-name 和 call-by-value的运行结果是什么?
我是 Haskell 的新手,我有一个问题
h x = x : (h x)
g xs = [head xs, head xs - 1]
g(h 2)
假设语义是 call-by-name 和 call-by-value的运行结果是什么?
“按名称调用”是非记忆非严格评估策略,其中参数的值只需要在函数体内实际使用时才需要找到,每次都重新:
h x = x : (h x)
g xs = [head xs, head xs - 1]
g (h 2) = let {xs = (h 2)} in [head xs, head xs - 1]
= [let {xs = (h 2)} in head xs, let {xs = (h 2)} in head xs - 1]
= [head (h 2), let {xs = (h 2)} in head xs - 1]
= [head (let {x = 2} in x : (h x)}), let {xs = (h 2)} in head xs - 1]
= [let {x = 2} in x, let {xs = (h 2)} in head xs - 1]
= [2, let {xs = (h 2)} in head xs - 1]
= ....
“按需调用”是记忆非严格的又名“惰性”评估策略,其中参数的值仅在第一次在函数体内使用时才需要找到,然后可用于任何进一步参考:
h x = x : (h x)
g xs = [head xs, head xs - 1]
g (h 2) = let {xs = (h 2)} in [head xs, head xs - 1]
= let {xs = (2 : (h 2))} in [head xs, head xs - 1]
= let {xs = (2 : (h 2))} in [2, head xs - 1]
= ....
“按值调用”是严格的评估策略,其中参数的值必须在进入函数体之前找到:
h x = x : (h x)
g xs = [head xs, head xs - 1]
g (h 2) = let {xs = (h 2)} in [head xs, head xs - 1]
= let {xs = (2 : (h 2))} in [head xs, head xs - 1]
= let {xs = (2 : (2 : (h 2)))} in [head xs, head xs - 1]
= let {xs = (2 : (2 : (2 : (h 2))))} in [head xs, head xs - 1]
= ....
以上所有假设g (h 2)
都是在 GHCi 提示符下输入的,因此需要由它完整打印。
让我们一步一步走时尚。
首先,h x = x : (h x)
是递归的。该函数h
是根据自身定义的:给定一些x
它把它放在 上list
,它是x : x : x : ...
无限的。换句话说,由于递归永远不会终止,因此列表不可能是有限的。然而它不会永远挂起。为什么?
因为 Haskell 很懒惰。除非绝对必要,否则它不会评估任何表达式。在您的情况下,根本不会在内存中创建无限列表,如果h 2
调用事件。为什么?
因为g
只请求给定列表的两个第一个元素,名为xs
. Haskell 足够聪明,可以根据需要扩展无限列表,而不是浪费资源。一般来说,这就是懒惰的好处。它也适用于其他表达式,而不仅仅是列表。