我意识到在 Haskell 中许多程序构造并似乎存储了一些中间结果,例如上面代码中的 groupsOf 列表。
“似乎”在这里说得很好,因为老实说,这就是 Haskell 真正发光的地方。事实上,由于懒惰,它可以占用更少的内存,而无需您进行复杂的微管理。
惰性 IO(例如 )的一个妙处readFile
是它只会根据需要读取尽可能多的文件,并且可以在您使用其内容时对文件进行垃圾收集。(嗯,大致如此。这取决于您如何设置缓冲。)
这是您的程序将如何执行的粗略草图:
t <- readFile "p8.log"
为t :: String
. 可能检查文件是否存在,并以读取模式打开它。
let digits = map digitToInt $concat $ lines t
为digits :: [Int]
print $ problem_8 digits
一旦我们尝试执行此操作,所有工作实际上都会完成。我们需要充分评估problem_8 digits :: Int
才能打印出来。所以我们创建一个thunk forproblem_8 digits
并强制它。
maximum . map product . groupsOf 5 $ digits
此时,maximum
需要前两个元素,map product . groupsOf 5 $ digits
才能看两者哪个更大。因此map product
需要查看前两个元素groupsOf 5 digits
来传递。
现在,groupsOf 5
将需要前 5 个元素digits
来生成它的第一个元素。此时,可能会读取文件的第一行,并进行定义的转换。groupsOf
可以构造它的第一个元素,并且可能构造它的第二个元素(假设该行上有超过 6 个字符)。groupsOf 5 digits
将它的前两个元素传递到链上,我们映射product
到这两个东西上,然后maximum
检查两者中哪个更大。我们保留两者中较大的结果。
在这一点上(实际上比这一点更早)我们可以完全丢弃中间结果。的前两个元素groupOf 5 digits
现在完全不需要了;我们永远不需要再次检查它们,垃圾收集器可以随时收集它们。该文件的前两个字符也不再使用,可以丢弃。
现在,这是非常随意的,可能有点不准确。但是你明白吗?Haskell 是惰性的(从技术上讲,Haskell 是“非严格的”,通常通过惰性实现),这使得它的执行风格与任何严格的语言都有很大不同。这使我们能够使用看似大量的中间表示和数据结构,而实际上并不占用大量内存。好的编译器,比如 GHC,可以优化内存使用,就像你不会相信的那样。