简体   繁体   English

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

[英]How to takeWhile elements in a list wrapped in a monad

Got a little puzzle I was wondering if you could help me clarify. 有点谜题我想知道你是否可以帮我澄清一下。

Let's define a function that returns a list: 让我们定义一个返回列表的函数:

let f = replicate 3

What we want to do is map this function to an infinite list, concatenate the results, and then take only things that match a predicate. 我们想要做的是将此函数映射到无限列表,连接结果,然后只获取与谓词匹配的内容。

takeWhile (< 3) $ concatMap f [1..]

Great! 大! That returns [1,1,1,2,2,2] , which is what I want. 返回[1,1,1,2,2,2] ,这就是我想要的。

Now, I want to do something similar, but the function f now wraps its results in a Monad. 现在,我想做类似的事情,但函数f现在将其结果包装在Monad中。 In my usecase, this is the IO monad, but this works for discussing my problem: 在我的用例中,这是IO monad,但这适用于讨论我的问题:

let f' x = Just $ replicate 3 x

To map and concat, I can use: 要映射和连接,我可以使用:

fmap concat $ mapM f' [1..5]

That returns: Just [1,1,1,2,2,2,3,3,3,4,4,4,5,5,5] 返回: Just [1,1,1,2,2,2,3,3,3,4,4,4,5,5,5]

If I want to use takeWhile , this still works: 如果我想使用takeWhile ,这仍然有效:

fmap (takeWhile (< 3) . concat) $ mapM f' [1..5]

Which returns: Just [1,1,1,2,2,2]. 返回:只[1,1,1,2,2,2]。 Great! 大!

But, if I make the list over which I map an infinite list this does not do what I expected: 但是,如果我列出了无限列表的列表,那么这不符合我的预期:

fmap (takeWhile (< 3) . concat) $ mapM f' [1..]

Seems like the takeWhile is never happening. 看起来像takeWhile永远不会发生。 Somehow, I'm not getting the lazy computation I was expecting. 不知何故,我没有得到我期待的懒惰计算。 I'm a bit lost. 我有点迷茫。

The problem isn't that fmap + takeWhile doesn't work with infinite lists wrapped in a monad. 问题不在于fmap + takeWhile不适用于包含在monad中的无限列表。 The problem is that mapM can't produce an infinite list (at least not in the Maybe monad). 问题是mapM无法生成无限列表(至少不在Maybe monad中)。

Think about it: If f' returns Nothing for any item in the list, mapM has to return Nothing . 想一想:如果f'为列表中的任何项返回Nothing ,则mapM必须返回Nothing However mapM can't know whether that will happen until it has called f' on all items in the list. 但是, mapM无法知道是否会在列表中的所有项目上调用f'发生这种情况。 So it needs to iterate through the whole list before it knows whether the result is Nothing or Just . 所以它需要在知道结果是Nothing还是Just之前遍历整个列表。 Obviously that's a problem with infinite lists. 显然这是无限列表的问题。

This should do the trick: 这应该做的伎俩:

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

See sepp2k's answer for an explanation of why you are losing laziness. 请参阅sepp2k的答案,解释为什么你会失去懒惰。 The Identity monad or the nonempty list monad, for example, wouldn't have this problem. 例如,Identity monad或非列表monad不会出现此问题。

You can't mapM an infinite list of Maybes. 你无法映射出无限的Maybes列表。 mapM is map followed by sequence. mapM是map,后跟sequence。 Here is the definition of sequence: 这是序列的定义:

sequence ms = foldr k (return []) ms
  where
    k m m' = do { x <- m; xs <- m'; return (x:xs) }

From this we see that sequence evaluates every monadic value in the list. 由此我们看到序列评估列表中的每个monadic值。 Since it's an infinite list, this operation will not terminate. 由于它是无限列表,因此该操作不会终止。

EDIT: 编辑:

luqui and Carl make a good point that this doesn't generalize to any monad. luqui和Carl提出了一个很好的观点,即这并不适用于任何monad。 To see why it doesn't work for Maybe we need to look at the implementation of (>>=): 要了解它为什么不适用于Maybe,我们需要查看(>> =)的实现:

(>>=) m k = case m of
    Just x  -> k x
    Nothing -> Nothing

The important point here is that we do a case on m. 这里重点是我们在m上做一个案例。 This makes the m strict because we have to evaluate it to figure out how to continue execution. 这使得m严格,因为我们必须对其进行评估以确定如何继续执行。 Note that we're not casing on x here, so it remains lazy. 请注意,我们在这里没有封装x,所以它仍然是懒惰的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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