下面mapAndSum
代码块中的函数结合了map
and sum
(别介意sum
在主函数中应用了另一个,它只是为了使输出紧凑)。是惰性计算的map
,而sum
是使用累加参数计算的。这个想法是,map
可以在内存中没有完整列表的情况下使用结果,并且(仅)之后sum
可以“免费”使用。main 函数表明我们在调用mapAndSum
. 让我解释一下这个问题。
根据 Haskell 标准,无可辩驳的模式示例let (xs, s) = mapAndSum ... in print xs >> print s
被翻译成
(\ v -> print (case v of { (xs, s) -> xs })
>> print (case v of { (xs, s) -> s }))
$ mapAndSum ...
因此,两个print
调用都携带对整个对的引用,这导致整个列表被保存在内存中。
我们(我的同事 Toni Dietze 和我)使用明确的case
声明(比较“bad”与“good2”)解决了这个问题。顺便说一句,发现这一点花了我们相当多的时间..!
现在,我们不明白的是两个方面:
为什么
mapAndSum
首先工作?它还使用了一种无可辩驳的模式,因此它也应该将整个列表保存在内存中,但显然不是。并且将 the 转换let
为 acase
会使函数表现得完全不懒惰(以至于堆栈溢出;没有双关语的意思)。我们查看了 GHC 生成的“核心”代码,但据我们所知,它实际上包含与上述相同的翻译
let
。所以这里没有线索,而是更多的混乱。为什么会“坏”?关闭优化时工作,但打开时不工作?
关于我们的实际应用的一个评论:我们希望实现一个大文本文件的流处理(格式转换),同时还积累一些值,然后写入一个单独的文件。如前所述,我们成功了,但两个问题仍然存在,答案可能会提高我们对 GHC 的理解,以应对即将到来的任务和问题。
谢谢!
{-# LANGUAGE BangPatterns #-}
-- Tested with The Glorious Glasgow Haskell Compilation System, version 7.4.2.
module Main where
import Control.Arrow (first)
import Data.List (foldl')
import System.Environment (getArgs)
mapAndSum :: Num a => (a -> b) -> [a] -> ([b], a)
mapAndSum f = go 0
where go !s (x : xs) = let (xs', s') = go (s + x) xs in (f x : xs', s')
-- ^ I have no idea why it works here. (TD)
go !s [] = ([], s)
main :: IO ()
main = do
let foo = mapAndSum (^ (2 :: Integer)) [1 .. 1000000 :: Integer]
let sum' = foldl' (+) 0
args <- getArgs
case args of
["bad" ] -> let (xs, s) = foo in print (sum xs) >> print s
["bad?"] -> print $ first sum' $ foo
-- ^ Without ghc's optimizer, this version is as memory
-- efficient as the “good” versions
-- With optimization “bad?” is as bad as “bad”. Why? (TD)
["good1"] -> print $ first' sum' $ foo
where first' g (x, y) = (g x, y)
["good2"] -> case foo of
(xs, s) -> print (sum' xs) >> print s
["good3"] -> (\ (xs, s) -> print (sum' xs) >> print s) $ foo
_ -> error "Sorry, I do not understand."