Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/elixir/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Haskell 我被哈斯克尔的懒惰评估弄糊涂了_Haskell_Lazy Evaluation - Fatal编程技术网

Haskell 我被哈斯克尔的懒惰评估弄糊涂了

Haskell 我被哈斯克尔的懒惰评估弄糊涂了,haskell,lazy-evaluation,Haskell,Lazy Evaluation,我担心Haskell懒惰评估的效率。 考虑下面的代码 main = print $ x + x where x = head [1..] 这里,由于懒惰,x首先保留head[1..]的表达式,而不是结果1, 但是,当我调用x+x时,表达式头[1..]会被执行两次吗 我在haskell.org上找到了以下描述 另一方面,惰性计算意味着仅在需要表达式结果时才对其进行计算。注意,从减少到计算的转变。因此,当求值引擎看到表达式时,它会构建一个thunk数据结构,其中包含求值表达式所需的任何值,以

我担心Haskell懒惰评估的效率。 考虑下面的代码

main = print $ x + x
   where x = head [1..]
这里,由于懒惰,x首先保留head[1..]的表达式,而不是结果1, 但是,当我调用x+x时,表达式头[1..]会被执行两次吗

我在haskell.org上找到了以下描述

另一方面,惰性计算意味着仅在需要表达式结果时才对其进行计算。注意,从减少到计算的转变。因此,当求值引擎看到表达式时,它会构建一个thunk数据结构,其中包含求值表达式所需的任何值,以及指向表达式本身的指针。当实际需要结果时,计算引擎调用表达式,然后用结果替换thunk以供将来参考

那么这是否意味着,在x+x中,当调用第一个x时,执行head[1..],并将x重新分配给1,而第二个x只是调用它的引用


我理解得对吗?

计算表达式有两种方法:

首先计算最外层。 首先要严格评估最内层。 考虑以下功能:

select x y z = if x > z then x else y
现在让我们称之为:

select (2 + 3) (3 + 4) (1 + 2)
这将如何评估

严格评估:首先评估最内层

select (2 + 3) (3 + 4) (1 + 2)

select 5 (3 + 4) (1 + 2)

select 5 7 (1 + 2)

select 5 7 3

if 5 > 3 then 5 else 7

if True then 5 else 7

5
select (2 + 3) (3 + 4) (1 + 2)

if (2 + 3) > (1 + 2) then (2 + 3) else (3 + 4)

if 5 > (1 + 2) then 5 else (3 + 4)

if 5 > 3 then 5 else (3 + 4)

if True then 5 else (3 + 4)

5
严格评估采取了6项措施。要计算select,首先必须计算它的参数。在严格求值中,函数的参数始终是完全求值的。因此,函数是按值调用的。因此,没有额外的簿记

惰性评估:首先评估最外层

select (2 + 3) (3 + 4) (1 + 2)

select 5 (3 + 4) (1 + 2)

select 5 7 (1 + 2)

select 5 7 3

if 5 > 3 then 5 else 7

if True then 5 else 7

5
select (2 + 3) (3 + 4) (1 + 2)

if (2 + 3) > (1 + 2) then (2 + 3) else (3 + 4)

if 5 > (1 + 2) then 5 else (3 + 4)

if 5 > 3 then 5 else (3 + 4)

if True then 5 else (3 + 4)

5
懒惰评估只减少了5次。我们从未使用过3+4,因此从未对其进行评估。在惰性计算中,我们可以计算函数而不计算其参数。参数仅在需要时计算。因此,函数是根据需要调用的

然而,按需调用求值策略需要额外的簿记——您需要跟踪表达式是否已求值。在上面的表达式中,当我们计算x=2+3时,我们不需要再次计算它。然而,我们确实需要跟踪是否对其进行了评估

Haskell支持严格和惰性评估。但是,默认情况下,它支持延迟计算。为了实现严格的评估,您必须使用特殊的和函数


类似地,您可以在严格的语言(如JavaScript)中进行延迟计算。但是,您需要跟踪表达式是否已求值。您可以研究如何用JavaScript或类似语言实现thunks。

这更多的是关于特定Haskell实现的问题,而不是关于Haskell本身的问题,因为该语言对如何评估事物没有特别的保证

但就我所知,在GHC和大多数其他实现中:是的,当对thunk进行评估时,它们会被内部结果替换,因此对同一thunk的其他引用会从第一次对其进行评估的工作中受益

需要注意的是,对于哪些表达式最终实现为对同一thunk的引用,并没有真正的保证。通常,只要结果相同,编译器就可以对代码进行它喜欢的任何转换。当然,在编译器中实现代码转换的原因通常是为了使代码更快,因此希望不太可能以使事情变得更糟的方式重写代码,但它永远不可能是完美的

但是在实践中,您通常会非常安全地假设,无论何时您给表达式起一个名称,如where x=head[1..],那么在绑定范围内该名称的所有使用都将是对单个thunk的引用。

首先,x只是一个。你可以看到如下情况:

λ Prelude> let x = head [1..]
λ Prelude> :sprint x
x = _
这里的u表示尚未对x进行评估。它的定义被记录下来

然后,只要认识到x是指向这个thunk的指针,就可以理解x+x是如何构造的:这两个x都指向同一个thunk。一旦一个被评估,另一个是,因为它是相同的重击

您可以通过ghc vis看到:

应该向您展示以下内容:

在这里你可以看到x+x thunk实际上指向x thunk两次

现在,如果您计算x,请打印它,例如:

λ Prelude> print x
您将获得:

您可以在这里看到,x thunk不再是thunk:它是值1