Функция 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
несут ссылку на всю пару, что приводит к хранению всего списка в памяти.
Мы (мой коллега Тони Дитце и я) решили это с помощью явного выражения case
(сравните "плохо" с "хорошим2" ). Кстати, выяснив это, нам пришлось немало времени...!
Теперь, что мы не понимаем, в два раза:
-
Почему
mapAndSum
работает в первую очередь? Он также использует неопровержимый шаблон, поэтому он также должен хранить весь список в памяти, но, очевидно, этого не делает. И преобразованиеlet
вcase
приведет к тому, что функция будет вести себя совершенно неинтересно (до того, что стек переполнится, а не каламбур).Мы рассмотрели "основной" код, созданный 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 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."