-1

我是 Haskell 的新手,我有一个问题

h x = x : (h x)
g xs = [head xs, head xs - 1]

g(h 2)假设语义是 call-by-name 和 call-by-value的运行结果是什么?

4

2 回答 2

3

“按名称调用”是非记忆非严格评估策略,其中参数的值只需要函数体内实际使用时才需要找到,每次都重新:

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 提示符下输入的,因此需要由它完整打印。

于 2020-05-05T06:00:42.647 回答
0

让我们一步一步走时尚。

首先,h x = x : (h x)是递归的。该函数h是根据自身定义的:给定一些x它把它放在 上list,它是x : x : x : ...无限的。换句话说,由于递归永远不会终止,因此列表不可能是有限的。然而它不会永远挂起。为什么?

因为 Haskell 很懒惰。除非绝对必要,否则它不会评估任何表达式。在您的情况下,根本不会在内存中创建无限列表,如果h 2调用事件。为什么?

因为g只请求给定列表的两个第一个元素,名为xs. Haskell 足够聪明,可以根据需要扩展无限列表,而不是浪费资源。一般来说,这就是懒惰的好处。它也适用于其他表达式,而不仅仅是列表。

于 2020-05-04T20:41:39.480 回答