2013-03-21 53 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)。但是,我無法在Haskell標準庫中找到與safeMapM類似的功能。

使用mapM(或sequence)仍然被認爲是良好做法嗎?
如果是這樣,儘管存在堆棧空間溢出的危險,爲什麼它被認爲是良好的做法?
如果不是您建議使用哪種替代方案?

+0

也許'mapM'更快,如果它不溢出,因爲你不必'倒退'?你測量過了嗎? – 2013-03-21 14:08:14

+0

你可以發佈你用來測試的'Main'模塊嗎? – jberryman 2013-03-21 14:12:46

+4

此外,還有一些單子(如'Control.Monad.State.Lazy'),其中'take 100 <$> mapM id [1 ..]'終止。 'take 100 <$> safeMapM id [1 ..]'不可能終止,不管單子是什麼 – 2013-03-21 15:06:05

回答

9

作爲Niklas B.,mapM的語義是一個有效的正確摺疊的語義,它在比翻轉版本更多的情況下成功終止。一般而言,mapM更有意義,因爲我們很少想要在龐大的數據列表上做出產生結果的地圖。更常見的是,我們想要評估效應的這種列表,並且在這種情況下,mapM_sequence_會丟棄結果,通常是推薦的。

編輯:換句話說,儘管在這個問題提出的問題,mapMsequence常用且通常被認爲是很好的做法。

+1

我希望我的代碼能夠在所有情況下運行,而不僅僅是「更多情況下」。很多效果都帶有返回值,例如用戶輸入。 Niklas B.示例我可以在開發時捕獲,部署後可能會發生堆棧空間溢出,並且可能會發生大型列表溢出。更難以找到imho – jonnydee 2013-03-21 17:57:27

+2

有不同的語義和不同的權衡。所以你需要選擇一個適合你的情況。這就是答案!唯一的答案!您提供的其他代碼也會在足夠大的列表上失敗 - 它只會佔用較大的列表。你也可以在程序中添加堆棧空間。只是,你知道,瞭解權衡,然後做出選擇。 – sclv 2013-03-21 18:25:17

6

如果是這樣,儘管存在堆棧空間溢出的危險,爲什麼它被認爲是良好的做法? 如果不是您建議使用哪種替代方案?

如果要處理生成的列表元素,請使用pipesconduit。兩者都不會建立中間名單。

我會顯示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 

現在我可以在ProducerConsumer使用(>->)連接,然後運行使用runProxy結果:

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

那麼這將讀取用戶的Int S和回聲他們回到控制檯,因爲他們沒有在內存中保存多個元素。

所以通常情況下,如果你想要一個有效的計算來生成元素流並立即使用它們,你不需要mapM。使用合適的流媒體庫。

如果您想了解更多關於pipes的信息,我建議您撥打reading the tutorial

+0

這個答案有點過時了。你可能想要更新它。 – dfeuer 2017-08-02 17:25:18

-1

如果你想留在懶惰的營地,lazyio包允許你懶惰地處理輸入列表。取而代之的

mapM f 

你寫

import qualified System.IO.Lazy as LazyIO 

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

沒有棧溢出了。

相關問題