繁体   English   中英

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

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM