[英]How to takeWhile elements in a list wrapped in a monad
有點謎題我想知道你是否可以幫我澄清一下。
讓我們定義一個返回列表的函數:
let f = replicate 3
我們想要做的是將此函數映射到無限列表,連接結果,然后只獲取與謂詞匹配的內容。
takeWhile (< 3) $ concatMap f [1..]
大! 返回[1,1,1,2,2,2]
,這就是我想要的。
現在,我想做類似的事情,但函數f現在將其結果包裝在Monad中。 在我的用例中,這是IO monad,但這適用於討論我的問題:
let f' x = Just $ replicate 3 x
要映射和連接,我可以使用:
fmap concat $ mapM f' [1..5]
返回: Just [1,1,1,2,2,2,3,3,3,4,4,4,5,5,5]
如果我想使用takeWhile
,這仍然有效:
fmap (takeWhile (< 3) . concat) $ mapM f' [1..5]
返回:只[1,1,1,2,2,2]。 大!
但是,如果我列出了無限列表的列表,那么這不符合我的預期:
fmap (takeWhile (< 3) . concat) $ mapM f' [1..]
看起來像takeWhile
永遠不會發生。 不知何故,我沒有得到我期待的懶惰計算。 我有點迷茫。
問題不在於fmap
+ takeWhile
不適用於包含在monad中的無限列表。 問題是mapM
無法生成無限列表(至少不在Maybe monad中)。
想一想:如果f'
為列表中的任何項返回Nothing
,則mapM
必須返回Nothing
。 但是, mapM
無法知道是否會在列表中的所有項目上調用f'
發生這種情況。 所以它需要在知道結果是Nothing
還是Just
之前遍歷整個列表。 顯然這是無限列表的問題。
這應該做的伎倆:
takeWhileM :: (Monad m) => (a -> Bool) -> [m a] -> m [a]
takeWhileM p [] = return []
takeWhileM p (m:ms) = do
x <- m
if p x
then liftM (x:) (takeWhileM p ms)
else return []
請參閱sepp2k的答案,解釋為什么你會失去懶惰。 例如,Identity monad或非空列表monad不會出現此問題。
你無法映射出無限的Maybes列表。 mapM是map,后跟sequence。 這是序列的定義:
sequence ms = foldr k (return []) ms where k m m' = do { x <- m; xs <- m'; return (x:xs) }
由此我們看到序列評估列表中的每個monadic值。 由於它是無限列表,因此該操作不會終止。
編輯:
luqui和Carl提出了一個很好的觀點,即這並不適用於任何monad。 要了解它為什么不適用於Maybe,我們需要查看(>> =)的實現:
(>>=) m k = case m of
Just x -> k x
Nothing -> Nothing
這里重點是我們在m上做一個案例。 這使得m嚴格,因為我們必須對其進行評估以確定如何繼續執行。 請注意,我們在這里沒有封裝x,所以它仍然是懶惰的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.