如何将包含在monad中的列表中的元素取出来

时间:2010-12-09 23:51:07

标签: list haskell types monads

有一点想法,我想知道你是否可以帮我澄清一下。

让我们定义一个返回列表的函数:

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。不知何故,我没有得到我期待的懒惰计算。我有点失落。

3 个答案:

答案 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,所以它仍然是懒惰的。