Неопровержимый паттерн не приводит к утечке памяти при рекурсии, но почему?

Функция 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."
14
задан Jonas Kölker 20 May 2014 в 13:40
поделиться