在Haskell中列出惰性求值

时间:2015-04-02 22:23:56

标签: haskell lazy-evaluation

我在下面做了一些简单的假设来理解Haskell中的列表延迟评估,

head [1, 2]                -- expr1
head [1 .. 2]              -- expr2
head [1 ..]                -- expr3

head . (1 :) $ []          -- eval1
head . (1 :) . (2 :) $ []  -- eval2

我认为expr3会像eval1那样被懒惰地评估,expr1expr2怎么样?

一般来说,

  • 是否在Haskell中对编译和运行时的技术进行了延迟评估?
  • 哪里说效率好但很难推理,按时,空间复杂或程序逻辑?

3 个答案:

答案 0 :(得分:6)

术语"懒惰评估"在很多方面使用。

  1. 懒惰评估是一种语义;它说明了哪些表达式评估哪些值。 (我承认语义中缺少一些细节!)
  2. 懒惰评估是一种实施策略;它提供了一种方式来运行" lambda演算术语符合上述语义,并使用共享来改善更明显的"名字呼叫的时间和内存使用情况。实施战略。
  3. 我认为你是以第三种方式使用它。
  4. 在其余部分,我将使用"懒惰评估"对于实施策略和"非严格语义"对于语义。

      

    我认为expr3会像eval1一样被懒惰地评估,expr1expr2怎么样?

    非严格语义规定对所有五个术语的评估应终止并生成值1,因此任何符合要求的实现都将以这种方式运行。延迟评估将在每个表达式的大约相同的空间和时间内执行此操作。我希望GHC会选择延迟评估,如果您强制执行这五个术语中的任何一个,尽管优化它可能会在编译时执行评估。如果您对此感兴趣,可以通过传递-ddump-simpl标志来自行检查。

      

    Haskell中的懒惰评估是编译和运行时的技术吗?

    希望上面的讨论已经澄清了这个问题。非严格语义描述了编译时和运行时之间的特定连接(也就是说,编译器必须生成一个程序,其运行时行为产生语义指定的值)。延迟评估是用于生成符合语义的程序的特定实现策略。 GHC有时会在其计划中使用惰性评估,但有时会使用其他实施策略;但是,它符合非严格的语义。 (如果你找到一个没有的地方,这就是一个错误!)

      

    哪里说效率好,但很难推断,按时,空间复杂或程序逻辑?

    非严格语义通常不会说明在计算过程中使用了多少时间或空间,所以如果你想对此进行推理,你需要完全不同的技术。即使您决定将您的推理限制为使用延迟评估实现的程序,事情也可能很困难。考虑像[1..]这样的表达式:它使用了多少空间?这个问题不能在真空中回答;懒惰评估的基本思想是让消费者对价值构建的价值进行控制。因此,如果没有使用表达式[1..]查看所做的程序,我们就不会知道多少。它可能会抛弃价值,在这种情况下几乎不会使用任何空间;或者它可能沿着列表走,在这种情况下使用恒定的空间;或者它可能在不同的时间遍历列表两次,在这种情况下使用无界空间;或者它可以做其他空间要求的其他一百万件。

答案 1 :(得分:2)

列表没什么特别之处。它们只是递归数据类型:

data [a] = a : [a] | []

现在当您使用[1 .. 2]时,直接转换为列表(1:(2:[]))!它将存储为表达式[1 .. 2]

现在head定义为:

head :: [a] -> a
head (x:_) = x

如果你调用head [1 .. 2],进入main(因此Haskell被迫以某种方式对其进行评估),它会看到[1 .. 2]不是数据结构,而是一个未解析的表达式,它将稍微解决一下表达式:

[1 .. 2] to (1:[(succ 1) .. 2])

因此现在读到:

head (1:[(succ 1) .. 2])

(请注意尾部仍然是一个表达式),但由于head只对 - 好 - “头”感兴趣,它将返回1。请注意,如果head例如是1+2,则不会立即将此评估为3

此外,如果你只是简单地调用head [1 .. 2]表达式不会被评估,那么只有你想要显示结果时,Haskell才会努力计算它。

根据编译器实现,编译器可以在编译时努力传播常量(文字)并对它们执行操作,但由于编译器应始终遵循执行标准,因此语义保持不变。 / p>

答案 2 :(得分:2)

要完成其他答案,您可以使用ghci中的:sprint命令检查延迟评估的工作方式:

Prelude> let xs = [1..10] :: [Int]
Prelude> :sprint xs
xs = _
Prelude> head xs
1
Prelude> :sprint xs
xs = 1 : _
Prelude> take 3 xs
[1,2,3]
Prelude> :sprint xs
xs = 1 : 2 : 3 : _
Prelude> length xs
10
Prelude> :sprint xs
xs = [1,2,3,4,5,6,7,8,9,10]