Функция mapAndSum
в приведенном ниже кодовом блоке объединяет map
и 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
несут ссылку на всю пару, что приводит к тому, что весь список хранится в памяти.
Мы (с моим коллегой Тони Дитце и я )решили эту проблему с помощью явного 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'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."