[英]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.