mapAndSum
函数结合了map
和sum
(忽略主函数中应用的另一个sum
,它只是为了使输出紧凑)。map
是惰性计算的,而sum
是使用累加参数计算的。其思想是map
的结果可以在没有完整列表的情况下消耗,并且(仅)之后“免费”获得sum
。主函数表明我们在调用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
调用都携带有对整个pair的引用,这导致整个列表被保存在内存中。我们(我的同事Toni Dietze和我)使用了一个显式的
case
语句来解决这个问题(参见"bad" vs "good2")。顺便说一下,找出这个问题花费了我们相当多的时间..!现在,我们不理解的是两方面的:
为什么mapAndSum
能够正常工作?它也使用了一个无法匹配的模式,所以它应该保留整个列表在内存中,但显然它并没有。将let
转换为case
将使函数完全失去惰性(甚至会导致堆栈溢出;无任何双关语的意思)。
我们查看了GHC生成的“core”代码,但就我们的理解而言,它实际上包含与上面的let
相同的翻译。因此没有头绪,反而更加困惑。
为什么“bad?”在关闭优化时可以工作,但在打开优化时却不能工作?
谢谢!
{-# 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."
mapAnsSum
函数可以通过使用Data.Traversable中的mapAccumL
来进行改进。 - Laar