有一点想法,我想知道你是否可以帮我澄清一下。
让我们定义一个返回列表的函数:
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
。不知何故,我没有得到我期待的懒惰计算。我有点失落。
答案 0 :(得分:8)
问题不在于fmap
+ takeWhile
不适用于包含在monad中的无限列表。问题是mapM
无法生成无限列表(至少不会出现在Maybe monad中)。
考虑一下:如果f'
为列表中的任何项目返回Nothing
,则mapM
必须返回Nothing
。但是mapM
无法知道在列表中的所有项目上调用f'
之前是否会发生这种情况。因此,在知道结果是Nothing
还是Just
之前,需要遍历整个列表。显然这是无限列表的问题。
答案 1 :(得分:5)
这应该可以解决问题:
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或 nonmpty 列表monad不会出现此问题。
答案 2 :(得分:4)
你不能映射一个无限的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,我们需要查看(&gt;&gt; =)的实现:(>>=) m k = case m of
Just x -> k x
Nothing -> Nothing
这里重点是我们在m上做一个案例。这使得m严格,因为我们必须对其进行评估以确定如何继续执行。请注意,我们这里没有封装x,所以它仍然是懒惰的。