我目前正在消化Keegan McAllister的演讲 Why learn Haskell?,他在那里使用了这个片段
minimum = head . sort
举例说明Haskell的惰性求值,指出在Haskell中minimum
函数的时间复杂度为O(n)。然而,我认为这个例子有点学术化。因此,我要求一个更实际的例子,在这个例子中,大部分中间计算不能被轻易地抛弃。
我目前正在消化Keegan McAllister的演讲 Why learn Haskell?,他在那里使用了这个片段
minimum = head . sort
举例说明Haskell的惰性求值,指出在Haskell中minimum
函数的时间复杂度为O(n)。然而,我认为这个例子有点学术化。因此,我要求一个更实际的例子,在这个例子中,大部分中间计算不能被轻易地抛弃。
你是否曾经编写过人工智能程序?那么你一定深有体会,在树遍历函数中传递剪枝信息(例如最大深度,相邻分支的最小成本或其他类似信息)是多么令人烦恼。这意味着每次想要改进AI时,都必须重新编写树遍历函数。但有了惰性评估,这就不再是问题了:只需编写一次树遍历函数即可生成一个巨大的(甚至可能是无限的!)游戏树,让使用者决定需要消耗的部分。
想要编写一个显示大量信息的GUI吗?希望程序在运行时仍然快速吗?在其他语言中,您可能需要编写仅渲染可见场景的代码。但在Haskell中,您可以编写代码以渲染整个场景,然后稍后选择要观察的像素。同样地,要渲染复杂的场景吗?何不计算各种精细级别的无限场景序列,并在程序运行时选择最合适的场景?
你编写了一个代价高昂的函数,并决定为了加速它而进行备忘录化处理。在其他语言中,这需要构建一个数据结构,跟踪您已知函数输入的答案,并在看到新的输入时更新该结构。别忘了使其线程安全--如果我们真的需要速度,我们还需要并行性!但在Haskell中,你可以构建一个无限的数据结构,为每个可能的输入都创建一个条目,并评估与您关心的输入相对应的数据结构部分。纯洁性带来的不可变性自然保证了线程安全。
这里讲述的内容可能比之前的那些内容朴素一些。你是否曾经遇到过想要短路的东西不仅仅是&&
和||
?我肯定有!例如,我喜欢使用<|>
函数来组合Maybe
值:它获取其参数中实际具有值的第一个值。因此,Just 3 <|> Nothing = Just 3
;Nothing <|> Just 7 = Just 7
;Nothing <|> Nothing = Nothing
。此外,它还具有短路效应:如果发现第一个参数是Just
,则不会进行计算以确定第二个参数。
而且,<|>
不是内置在语言中的;它是由库添加的。也就是说:惰性允许您编写全新的短路形式。(实际上,在Haskell中,(&&)
和(||)
的短路行为甚至不是内置的编译器魔法:它们自然地从语言的语义加上标准库中它们的定义中产生。)
总的来说,这里的共同主题是您可以将值的生成与决定哪些值有趣分开。这使得事物更具可组合性,因为生产者不需要知道有趣的内容选择。
<|>
运算符相当于(Emacs-)Lisp 中对可能始终为 nil
的对象的 or
行为,对吧?无论如何,我真的很想在其他语言中使用这种行为。 - Nordlöw这是我昨天在另一个帖子中发布的众所周知的例子。哈明数指的是没有除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)
在非惰性语言中以合理的速度计算该数字需要编写更多代码并进行深思熟虑。这里有很多示例。
n
个元素。如果没有惰性计算,那么生成步骤将永远运行下去,而不会消耗任何东西。有了惰性计算,只要代码尝试消耗多少元素,就会生成多少元素。