簡體   English   中英

Haskell的mapM不是懶惰嗎?

[英]Is Haskell's mapM not lazy?

更新:好的,這個問題可能非常簡單。

q <- mapM return [1..]

為什么這永遠不會回來?


mapM是不是懶得處理無限列表?

下面的代碼掛起。 但是,如果我用線B替換A線,它就不會再掛起了。 或者,如果我在A行之前加上“splitRandom $”,它也不會掛起。

Q1是:mapM不是懶惰的嗎? 否則,為什么用線B替換A行“修復此”代碼?

Q2是:為什么前面的A行與splitRandom“解決”了這個問題?

import Control.Monad.Random
import Control.Applicative

f :: (RandomGen g) => Rand g (Double, [Double])
f = do
    b <- splitRandom $ sequence $ repeat $ getRandom
    c <- mapM return b -- A
    -- let c = map id b -- B
    a <- getRandom
    return (a, c)

splitRandom :: (RandomGen g) => Rand g a -> Rand g a
splitRandom code = evalRand code <$> getSplit

t0 = do
    (a, b) <- evalRand f <$> newStdGen
    print  a
    print (take 3 b)

該代碼懶惰地生成無限的隨機數列表。 然后它生成一個隨機數。 通過使用splitRandom,我可以在無限列表之前首先評估后一個隨機數。 如果我在函數中返回b而不是c,則可以證明這一點。

但是,如果我將mapM應用於列表,程序現在掛起。 為了防止這種情況發生,我必須在mapM之前再次應用splitRandom。 我的印象是mapM可以懶散

好吧,有懶惰,然后就是懶惰 mapM確實是懶惰的,因為它沒有做更多的工作。 但是,請查看類型簽名:

mapM :: (Monad m) => (a -> m b) -> [a] -> m [b]

想想這意味着什么:你給它一個功能a -> mb和一堆a秒。 常規map可以將它們變成一堆mb ,但不是m [b] b s組合成單個[b]而不使monad妨礙的唯一方法是使用>>=mb s排序在一起構造列表

實際上, mapM恰好等同於sequence . map sequence . map

通常,對於任何monadic表達式,如果完全使用該值,則必須強制導致表達式的整個>>= s鏈,因此將sequence應用於無限列表無法完成。

如果你想使用無界的monadic序列,你需要顯式的流控制 - 例如,以某種方式綁定到綁定鏈中的循環終止條件,像mapMsequence這樣的簡單遞歸函數不提供 - 或者一步一步的序列,如下所示:

data Stream m a = Nil | Stream a (m (Stream m a))

...所以你只需要強制盡可能多的monad圖層。

編輯::關於splitRandom ,正在發生的事情是你傳遞一個Rand計算,用種子splitRandom得到它,然后return結果。 如果沒有splitRandom ,單個getRandom使用的種子必須來自無限列表排序的最終結果,因此它會掛起。 使用額外的splitRandom ,使用的種子只需要穿過兩個splitRandom調用,因此它可以工作。 隨機數的最終列表是有效的,因為你已經離開了Rand monad並且沒有任何東西取決於它的最終狀態。

好的,這個問題可能非常簡單。

q < - mapM返回[1 ..]

為什么這永遠不會回來?

這不一定是真的。 這取決於你所在的monad。

例如,使用標識monad,您可以懶惰地使用結果並終止正常:

newtype Identity a = Identity a

instance Monad Identity where
  Identity x >>= k = k x
  return = Identity

-- "foo" is the infinite list of all the positive integers
foo :: [Integer]
Identity foo = do
  q <- mapM return [1..]
  return q

main :: IO ()
main = print $ take 20 foo -- [1 .. 20]

這是一個嘗試證明mapM return [1..]沒有終止。 讓我們暫時假設我們在Identity monad中(該參數同樣適用於任何其他monad):

mapM return [1..] -- initial expression
sequence (map return [1 ..]) -- unfold mapM
let k m m' = m >>= \x ->
             m' >>= \xs ->
             return (x : xs)
in foldr k (return []) (map return [1..]) -- unfold sequence

到現在為止還挺好...

-- unfold foldr
let k m m' = m >>= \x ->
             m' >>= \xs ->
             return (x : xs)
    go [] = return []
    go (y:ys) = k y (go ys)
in go (map return [1..])

-- unfold map so we have enough of a list to pattern-match go:
go (return 1 : map return [2..])
-- unfold go:
k (return 1) (go (map return [2..])
-- unfold k:
(return 1) >>= \x -> go (map return [2..]) >>= \xs -> return (x:xs)

回想一下,在Identity monad中return a = Identity a ,在Identity monad中return a = Identity a (Identity a) >>= f = fa 繼續:

-- unfold >>= :
(\x -> go (map return [2..]) >>= \xs -> return (x:xs)) 1
-- apply 1 to \x -> ... :
go (map return [2..]) >>= \xs -> return (1:xs)
-- unfold >>= :
(\xs -> return (1:xs)) (go (map return [2..]))

請注意,此時我們很樂意申請\\xs ,但我們還不能! 在我們有值申請之前,我們必須繼續展開:

-- unfold map for go:
(\xs -> return (1:xs)) (go (return 2 : map return [3..]))
-- unfold go:
(\xs -> return (1:xs)) (k (return 2) (go (map return [3..])))
-- unfold k:
(\xs -> return (1:xs)) ((return 2) >>= \x2 ->
                         (go (map return [3..])) >>= \xs2 ->
                         return (x2:xs2))
-- unfold >>= :
(\xs -> return (1:xs)) ((\x2 -> (go (map return [3...])) >>= \xs2 ->
                        return (x2:xs2)) 2)

此時,我們仍然不能申請\\xs ,但我們可以申請\\x2 繼續:

-- apply 2 to \x2 :
(\xs -> return (1:xs)) ((go (map return [3...])) >>= \xs2 ->
                         return (2:xs2))
-- unfold >>= :
(\xs -> return (1:xs)) (\xs2 -> return (2:xs2)) (go (map return [3..]))

現在我們已經達到了既不能降低\\xs 不能降低\\xs2 我們唯一的選擇是:

-- unfold map for go, and so on...
(\xs -> return (1:xs))
  (\xs2 -> return (2:xs2))
    (go ((return 3) : (map return [4..])))

所以你可以看到,因為foldr ,我們正在構建一系列要應用的函數,從列表的末尾開始,然后重新開始。 因為在每一步輸入列表都是無限的,所以這種展開永遠不會終止,我們永遠不會得到答案。

如果你看一下這個例子(借用另一個StackOverflow線程,我目前找不到哪一個),這是有道理的。 在以下monad列表中:

mebs = [Just 3, Just 4, Nothing]

我們希望sequence捕獲Nothing並返回整個事件的失敗:

sequence mebs = Nothing

但是,對於此列表:

mebs2 = [Just 3, Just 4]

我們希望序列給我們:

sequence mebs = Just [3, 4]

換句話說, sequence 必須查看整個monadic計算列表,將它們串在一起,並運行它們以便得出正確的答案。 如果沒有看到整個列表, sequence就無法給出答案。

注意:此答案的先前版本斷言, foldr從列表的后面開始計算,並且在無限列表上根本不起作用,但這是不正確的! 如果傳遞給foldr的運算符在其第二個參數上是惰性的,並使用惰性數據構造函數(如列表)生成輸出,則foldr將很樂意使用無限列表。 有關示例,請參見foldr (\\x xs -> (replicate xx) ++ xs) [] [1...] 但是我們的運營商k並非如此。

這個問題很好地展示了IO Monad和其他Monads之間的區別。 在后台,mapM在所有列表元素之間構建一個帶有綁定操作(>> =)的表達式,以將monadic表達式列表轉換為列表的monadic表達式。 現在,IO monad的不同之處在於Haskell的執行模型在IO Monad中的綁定期間執行表達式。 這正是最終迫使(在一個純粹的懶惰世界中)要執行的東西。

所以IO Monad在某種程度上是特殊的,它使用bind的序列范例來實際執行每一步的執行,這是我們的程序最終執行任何操作所做的。 其他Monads是不同的。 它們具有綁定運算符的其他含義,具體取決於Monad。 IO實際上是一個在綁定中執行事物的Monad,這就是為什么IO類型是“動作”的原因。

以下示例顯示其他Monads不強制執行,例如Maybe monad。 最后,這導致IO Monad中的mapM返回一個表達式,該表達式在執行時會在返回最終值之前執行每個單獨的元素。

有很好的論文,從這里開始或搜索指稱語義和Monads:解決尷尬的小隊: http ://research.microsoft.com/en-us/um/people/simonpj/papers/marktoberdorf/mark.pdf

Maybe Monad的例子:

模塊主要在哪里

fstMaybe :: [Int] - >也許[Int] fstMaybe = mapM(\\ x - >如果x == 3那么沒有別的只是x)

main = do r = fstMaybe [1 ..]返回r

讓我們在更通用的背景下討論這個問題。

正如其他答案所說, mapM只是sequencemap的組合。 所以問題是為什么sequence在某些Monad是嚴格的。 然而,這不僅限於Monads ,也不限於Applicative因為我們在大多數情況下都有sequenceA ,它們共享相同的sequence實現。

現在看一下sequenceA的(專用列表)類型簽名:

sequenceA :: Applicative f => [f a] -> f [a]

你會怎么做? 您獲得了一個列表,因此您希望在此列表中使用foldr

sequenceA = foldr f b where ...
  --f :: f a -> f [a] -> f [a]
  --b :: f [a]

由於fApplicative ,你知道什么b承擔責任:水災是- pure [] 但是什么是f 顯然它是(:)的升級版本:

(:) :: a -> [a] -> [a]

所以現在我們知道sequenceA是如何工作的:

sequenceA = foldr f b where
  f a b = (:) <$> a <*> b
  b = pure []

要么

sequenceA = foldr ((<*>) . fmap (:)) (pure [])

假設你有一個懶惰的列表(x:_|_) sequenceA的上述定義給出了

sequenceA (x:_|_) === (:) <$> x <*> foldr ((<*>) . fmap (:)) (pure []) _|_
                  === (:) <$> x <*> _|_

所以現在我們看到問題被減少到考慮天氣f <*> _|__|_或不。 顯然,如果f是嚴格的,則這是_|_ ,但如果f不嚴格,為了允許停止評估,我們要求<*>本身是非嚴格的。

因此,應用仿函數使sequenceA停止的標准將是<*>運算符非嚴格。 一個簡單的測試就是

const a <$> _|_ === _|_      ====> strict sequenceA
-- remember f <$> a === pure f <*> a

如果我們談論Moand ,那么標准就是

_|_ >> const a === _|_ ===> strict sequence

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM