我正在消化Keegan McAllister的精彩演示文稿为什么要学习Haskell?。他在那里使用了片段
minimum = head . sort
通过在Haskell中声明minimum
具有时间复杂度 O(n)来说明Haskell的懒惰评估。但是,我认为这个例子具有学术性质。因此,我要求一个更实际的例子,即大多数中间计算被抛弃,这并非显而易见。
答案 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
元素。没有惰性评估,天真编码将在生成步骤中永远运行,并且永远不会消耗任何东西。使用延迟评估时,只会生成与代码尝试使用的元素一样多的元素。