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

Dan*_*iel 4 monads haskell types list

有点谜题我想知道你是否可以帮我澄清一下.

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

let f = replicate 3
Run Code Online (Sandbox Code Playgroud)

我们想要做的是将此函数映射到无限列表,连接结果,然后只获取与谓词匹配的内容.

takeWhile (< 3) $ concatMap f [1..]
Run Code Online (Sandbox Code Playgroud)

大!返回[1,1,1,2,2,2],这就是我想要的.

现在,我想做类似的事情,但函数f现在将其结果包装在Monad中.在我的用例中,这是IO monad,但这适用于讨论我的问题:

let f' x = Just $ replicate 3 x
Run Code Online (Sandbox Code Playgroud)

要映射和连接,我可以使用:

fmap concat $ mapM f' [1..5]
Run Code Online (Sandbox Code Playgroud)

返回: 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]
Run Code Online (Sandbox Code Playgroud)

返回:只[1,1,1,2,2,2].大!

但是,如果我列出了无限列表的列表,那么这不符合我的预期:

fmap (takeWhile (< 3) . concat) $ mapM f' [1..]
Run Code Online (Sandbox Code Playgroud)

似乎takeWhile永远不会发生.不知何故,我没有得到我期待的懒惰计算.我有点迷茫.

sep*_*p2k 8

问题不在于fmap+ takeWhile不适用于包含在monad中的无限列表.问题是mapM无法生成无限列表(至少不在Maybe monad中).

想想看:如果f'返回Nothing列表中的任何项目,mapM必须返回Nothing.然而mapM,在它调用f'列表中的所有项目之前,无法知道是否会发生这种情况.因此,在知道结果是否为Nothing或之前,需要遍历整个列表Just.显然这是无限列表的问题.

  • 更一般地说,对于任何monad(其中(>> =)在其第一个参数中是严格的),mapM不是(也不可能)是懒惰的.这包括Maybe,[],IO和其他一些东西. (2认同)

luq*_*qui 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 []
Run Code Online (Sandbox Code Playgroud)

请参阅sepp2k的答案,解释为什么你会失去懒惰.例如,Identity monad或非列表monad不会出现此问题.

  • 请注意,迭代器为此问题提供了更通用的解决方案. (3认同)