簡體   English   中英

為什么Haskell序列函數不能是懶惰的,或者為什么遞歸monadic函數不能是懶惰的

[英]Why the Haskell sequence function can't be lazy or why recursive monadic functions can't be lazy

使用問題列出按廣度優先順序列出目錄的所有內容效率低下我了解到效率低是由於遞歸monad函數的奇怪行為。

嘗試

sequence $ map return [1..]::[[Int]]
sequence $ map return [1..]::Maybe [Int]

和ghci將陷入無休止的計算。

如果我們以更易讀的形式重寫序列函數,如下所示:

sequence' []     = return []
sequence' (m:ms) = do {x<-m; xs<-sequence' ms; return (x:xs)}

並嘗試:

sequence' $ map return [1..]::[[Int]]
sequence' $ map return [1..]::Maybe [Int]

我們得到了相同的情況,無休止的循環。

嘗試一個有限的列表

sequence' $ map return [1..]::Maybe [Int]

經過很長時間的等待,它會彈出預期的結果Just [1,2,3,4..]

根據我們的嘗試,我們可以得出結論,盡管序列的定義“似乎是懶惰的,但它是嚴格的,並且必須在序列結果之前得出所有數字”才能打印出來。

如果我們定義一個函數,不僅僅是序列'

iterateM:: Monad m => (a -> m a) -> a -> m [a]
iterateM f x = (f x) >>= iterateM0 f >>= return.(x:)

並嘗試

iterateM (>>=(+1)) 0

然后發生無休止的計算

眾所周知,非monadic迭代的定義與上面的iterateM類似,但為什么迭代是惰性的,而iterateM是嚴格的。 正如我們從上面所看到的,iterateM和sequence'都是遞歸的monadic函數。有一些奇怪的遞歸monadic函數

問題不在於sequence的定義,而是底層monad的操作。 特別是,monad的>>=操作的嚴格性決定了sequence的嚴格性。

對於一個足夠懶惰的monad,完全可以在無限列表上運行sequence並逐步消耗結果。 考慮:

Prelude>  :m + Control.Monad.Identity
Prelude Control.Monad.Identity> runIdentity (sequence $ map return [1..] :: Identity [Int])

並根據需要逐步打印(消費)列表。

使用Control.Monad.State.StrictControl.Monad.State.Lazy嘗試此操作可能很有啟發性:

-- will print the list
Prelude Control.Monad.State.Lazy> evalState (sequence $ map return [1..] :: State () [Int]) ()
-- loops
Prelude Control.Monad.State.Strict> evalState (sequence $ map return [1..] :: State () [Int]) ()

IO monad中, >>=按定義嚴格,因為這種嚴格性恰好是啟用效果排序推理所必需的屬性。 我認為@jberryman的答案很好地證明了“嚴格>>= ”的含義。 對於具有嚴格>>= IO和其他monad,必須在sequence返回之前評估列表中的每個表達式。 使用無限的表達式列表,這是不可能的。

你不是很熟悉綁定的機制:

(>>=) :: Monad m => m a -> (a -> m b) -> m b

這是一個僅適用於3長度列表的序列實現:

sequence3 (ma:mb:mc:[]) = ma >>= (\a-> mb >>= (\b-> mc >>= (\c-> return [a,b,c] )))

在我們返回外部構造函數(即最外面的缺點,或(:) )之前,你看到我們如何在列表中“運行”每個“monadic動作”? 如果您不相信,請嘗試以不同方式實施。

這是monad對IO有用的一個原因:當你綁定兩個動作時,會有一個隱含的效果排序。

您還必須小心使用“懶惰”和“嚴格”這兩個術語。 sequence是正確的,你必須遍歷整個列表才能包裝最終結果,但以下工作非常好:

Prelude Control.Monad> sequence3 [Just undefined, Just undefined, Nothing]
Nothing

Monadic sequence通常無法在無限列表上懶散地工作。 考慮它的簽名:

sequence :: Monad m => [m a] -> m [a]

它將論證中的所有monadic效果組合成單一效果。 如果將其應用於無限列表,則需要將無限多個效果合並為一個。 對於一些單子,有可能,對於一些單子,它不是。

例如,如您在示例中所做的那樣,考慮專門針對Maybe sequence

sequence :: [Maybe a] -> Maybe [a]

結果是Just ...如果數組中的所有元素都是Just ... 如果任何元素為Nothing則結果為Nothing 這意味着除非您檢查輸入的所有元素,否則您無法判斷結果是Nothing還是Just ...

這同樣適用於sequence專門到[] sequence :: [[a]] -> [[a]] 如果參數的任何元素是空列表,則整個結果是空列表,如sequence [[1],[2,3],[],[4]] 因此,為了評估列表列表中的sequence ,您必須檢查所有元素以查看結果的樣子。


另一方面,專門用於Reader monad的序列可以懶惰地處理它的參數,因為對於Reader的monadic計算沒有真正的“影響”。 如果你定義

inf :: Reader Int [Int]
inf = sequence $ map return [1..]

也許

inf = sequence $ map (\x -> reader (* x)) [1..]

它會懶惰地工作,正如你可以通過調用take 10 (runReader inf 3)看到的那樣。

暫無
暫無

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

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