非平凡的懒惰评估

时间:2011-10-23 19:16:56

标签: haskell time-complexity lazy-evaluation

我正在消化Keegan McAllister的精彩演示文稿为什么要学习Haskell?。他在那里使用了片段

minimum = head . sort

通过在Haskell中声明minimum具有时间复杂度 O(n)来说明Haskell的懒惰评估。但是,我认为这个例子具有学术性质。因此,我要求一个更实际的例子,即大多数中间计算被抛弃,这并非显而易见。

3 个答案:

答案 0 :(得分:75)

  • 你有没有写过AI?您是否必须通过树遍历功能来修剪修剪信息(例如最大深度,相邻分支的最小成本或其他此类信息)?这意味着每次想要改进AI时都必须编写新的树遍历。那是愚蠢的。通过懒惰的评估,这不再是一个问题:编写一次树遍历函数,生成一个巨大的(甚至可能是无限的!)游戏树,让消费者决定消耗多少。

  • 编写显示大量信息的GUI?希望它能够快速运行?在其他语言中,您可能必须编写仅呈现可见场景的代码。在Haskell中,您可以编写呈现整个场景的代码,然后选择要观察的像素。同样,渲染复杂的场景?为什么不在各种细节级别计算无限的场景序列,并在程序运行时选择最合适的场景?

  • 你写了一个昂贵的功能,并决定记住它的速度。在其他语言中,这需要构建一个数据结构来跟踪您知道答案的函数的输入,并在看到新输入时更新结构。记得让线程安全 - 如果我们真的需要速度,我们也需要并行性!在Haskell中,您构建了一个无限的数据结构,每个可能的输入都有一个条目,并评估与您关注的输入相对应的数据结构部分。螺纹安全免费提供纯净。

  • 这是一个可能比以前更平淡无奇的一个。你有没有找到&&||不是唯一想要短路的东西?我确定有!例如,我喜欢<|>函数来组合Maybe值:它的第一个参数实际上有一个值。所以Just 3 <|> Nothing = Just 3; Nothing <|> Just 7 = Just 7;和Nothing <|> Nothing = Nothing。此外,它是短路的:如果事实证明它的第一个参数是Just,那么它就不会费心去做第二个参数是什么所需的计算。

    <|>并非内置于该语言中;这是由图书馆强加的。也就是说:懒惰允许你编写全新的短路形式。 (实际上,在Haskell中,即使(&&)(||)的短路行为也不是内置的编译器魔术:它们自然地来自语言的语义加上它们在标准库中的定义。 )

一般来说,这里的共同主题是,您可以将值的生成与确定哪些值感兴趣的分开。这使得事物更具有可组合性,因为生产者无需知道选择有趣的内容。

答案 1 :(得分:7)

这是我昨天发布到另一个帖子的一个众所周知的例子。汉明数字是没有任何素数因子大于5的数字。它们的形式为2 ^ i * 3 ^ j * 5 ^ k。前20个是:

[1,2,3,4,5,6,8,9,10,12,15,16,18,20,24,25,27,30,32,36]

第500000个是:

1962938367679548095642112423564462631020433036610484123229980468750

打印第500000个的程序(经过短暂的计算后)是:

merge xxs@(x:xs) yys@(y:ys) =
  case (x`compare`y) of
    LT -> x:merge xs yys
    EQ -> x:merge xs ys
    GT -> y:merge xxs ys

hamming = 1 : m 2 `merge` m 3 `merge` m 5
  where
    m k = map (k *) hamming

main = print (hamming !! 499999)

在非惰性语言中以合理的速度计算这个数字需要更多的代码和头脑。有很多examples here

答案 2 :(得分:4)

考虑生成和使用无限序列的第一个n元素。没有惰性评估,天真编码将在生成步骤中永远运行,并且永远不会消耗任何东西。使用延迟评估时,只会生成与代码尝试使用的元素一样多的元素。