2013-03-21 3 views
15

Рассмотрим следующий пример:Использует mapM/последовательность считается хорошей практикой?

safeMapM f xs = safeMapM' xs [] 
    where safeMapM' []  acc = return $ reverse acc 
      safeMapM' (x:xs) acc = do y <- f x 
            safeMapM' xs (y:acc) 

mapM return largelist  -- Causes stack space overflow on large lists 
safeMapM return largelist -- Seems to work fine 

Использование mapM на больших списках вызывает переполнение стека пространства в то время как safeMapM, кажется, работает нормально (с использованием GHC 7.6.1 с -O2). Однако я не смог найти функцию, аналогичную safeMapM в стандартных библиотеках Haskell.

По-прежнему считается хорошей практикой использования mapM (или sequence в этом отношении)?
Если да, то почему это считается хорошей практикой, несмотря на опасность переполнения пространства стека?
Если нет, какой вариант вы предлагаете использовать?

+0

Возможно, 'mapM' быстрее, если он не переполняется, потому что вам не нужно« обращать »? Вы его измеряли? –

+0

Можете ли вы разместить модуль 'Main', который вы использовали для тестирования? – jberryman

+4

Кроме того, существуют монады (например, 'Control.Monad.State.Lazy'), где-то вроде' take 100 <$> mapM id [1 ..] 'завершается. 'take 100 <$> safeMapM id [1 ..]' не может завершиться, независимо от монады –

ответ

9

Как и Никлас Б., семантика mapM - это эффективная правая сгиб, и она успешно завершается в большинстве случаев, чем перевернутая версия. В общем, mapM имеет больше смысла, так как редко бывает, что мы захотим сделать карту, соответствующую результатам, в огромном списке данных. Чаще всего мы хотим оценить такой список для эффектов, и в этом случае mapM_ и sequence_, которые выбрасывают результаты, как правило, рекомендуются.

Edit: другими словами, несмотря на вопрос, поставленный в этом вопросе, да, mapM и sequence обычно используются и, как правило, считается хорошей практикой.

+1

Я хочу, чтобы мой код запускался во всех случаях, а не только в «других случаях». И много эффектов приходят с возвращаемыми значениями, например. пользовательский ввод. Niklas B. Например, я могу ловить во время разработки, переполнение стека пространства с большими списками может произойти после развертывания. Намного сложнее найти imho – jonnydee

+2

есть разные семантики и разные компромиссы. поэтому вам нужно выбрать тот, который подходит вашей ситуации. это ответ! единственный ответ! другой код, который вы предоставляете, также не будет работать в достаточно больших списках - он просто возьмет более крупные списки. вы также можете просто добавить пространство стека в программу. просто знаешь, понимаешь компромиссы, а потом делаешь выбор. – sclv

6

Если да, то почему это считается хорошей практикой, несмотря на опасность переполнения пространства стека? Если нет, какой вариант вы предлагаете использовать?

Если вы хотите обработать элементы списка как они генерируются, используют либо pipes или conduit. Оба никогда не будут создавать промежуточный список.

Я покажу путь pipes, так как это моя библиотека. Я первым начать с бесконечным списком чисел, сгенерированных в IO монады от пользовательского ввода:

import Control.Proxy 

infiniteInts :: (Proxy p) =>() -> Producer p Int IO r 
infiniteInts() = runIdentityP $ forever $ do 
    n <- lift readLn 
    respond n 

Теперь я хочу, чтобы напечатать их, как они генерируются. Это требует определения нижестоящего обработчика:

printer :: (Proxy p) =>() -> Consumer p Int IO r 
printer() = runIdentityP $ forever $ do 
    n <- request() 
    lift $ print n 

Теперь можно подключить Producer и Consumer с помощью (>->) и запустить результат, используя runProxy:

>>> runProxy $ infiniteInts >-> printer 
4<Enter> 
4 
7<Enter> 
7 
... 

Это будет прочитать Int с от пользователя и эхо их обратно в консоль, поскольку они генерируются без сохранения более одного элемента в памяти.

Как правило, если вы хотите произвести эффективное вычисление, которое генерирует поток элементов и потребляет их немедленно, вы не хотите mapM. Используйте соответствующую библиотеку потоковой передачи.

Если вы хотите узнать больше о pipes, тогда я рекомендую reading the tutorial.

+0

Этот ответ немного устарел. Вы можете обновить его. – dfeuer

-1

Если вы хотите остановиться в ленивом лагере, то пакет lazyio позволяет обрабатывать список входных данных лениво. Вместо

mapM f 

вы пишете

import qualified System.IO.Lazy as LazyIO 

LazyIO.run . mapM (LazyIO.interleave . f) 

Нет переполнения стека больше.

Смежные вопросы