简体   繁体   English

哈斯克尔:monadic takeWhile?

[英]Haskell: monadic takeWhile?

I have some functions written in C that I call from Haskell. 我有一些用C语言编写的函数,我从Haskell调用。 These functions return IO (CInt) . 这些函数返回IO (CInt) Sometimes I want to run all of the functions regardless of what any of them return, and this is easy. 有时我想运行所有函数,无论它们返回什么,这很容易。 For sake of example code, this is the general idea of what's happening currently: 为了示例代码,这是当前正在发生的事情的一般概念:

Prelude> let f x = print x >> return x
Prelude> mapM_ f [0..5]
0
1
2
3
4
5
Prelude>

I get my desired side effects, and I don't care about the results. 我得到了我想要的副作用,我不关心结果。 But now I need to stop execution immediately after the first item that doesn't return my desired result. 但是现在我需要在第一个没有返回我想要的结果的项目之后立即停止执行。 Let's say a return value of 4 or higher requires execution to stop - then what I want to do is this: 假设返回值为4或更高要求执行停止 - 那么我想要做的是:

Prelude> takeWhile (<4) $ mapM f [0..5]

Which gives me this error: 这给了我这个错误:

<interactive>:1:22:
    Couldn't match expected type `[b]' against inferred type `IO a'
    In the first argument of `mapM', namely `f'
    In the second argument of `($)', namely `mapM f ([0 .. 5])'
    In the expression: takeWhile (< 4) $ mapM f ([0 .. 5])

And that makes sense to me - the result is still contained in the IO monad, and I can't just compare two values contained in the IO monad. 这对我来说很有意义 - 结果仍然包含在IO monad中,我不能只比较IO monad中包含的两个值。 I know this is precisely the purpose of monads -- chaining results together and discarding operations when a certain condition is met -- but is there an easy way to "wrap up" the IO monad in this case to stop executing the chain upon a condition of my choosing, without writing an instance of MonadPlus ? 我知道这正是monad的目的 - 将结果链接在一起并在满足某个条件时丢弃操作 - 但在这种情况下是否有一种简单的方法来“封装”IO monad以在条件下停止执行链我选择的,没有编写MonadPlus的实例?

Can I just "unlift" the values from f , for the purposes of the takeWhile? 为了takeWhile的目的,我可以从f “解除”值吗?

Is this a solution where functors fit? 这是算子适合的解决方案吗? Functors haven't "clicked" with me yet, but I sort of have the impression that this might be a good situation to use them. Functors还没有和我“点击”,但我觉得这可能是一个使用它们的好方法。


Update: 更新:

@sth has the closest answer to what I want - in fact, that's almost exactly what I was going for, but I'd still like to see whether there is a standard solution that isn't explicitly recursive -- this is Haskell, after all! @sth对我想要的答案最接近 - 事实上,这几乎就是我想要的,但我仍然想看看是否有一个没有明确递归的标准解决方案 - 这是Haskell,之后所有! Looking back on how I worded my question, now I can see that I wasn't clear enough about my desired behavior. 回顾我如何措辞我的问题,现在我可以看到我对自己想要的行为不够清楚。

The f function I used above for an example was merely an example. 我上面用作示例的f函数仅仅是一个例子。 The real functions are written in C and used exclusively for their side effects. 实际功能是用C语言编写的,专门用于副作用。 I can't use @Tom's suggestion of mapM_ f (takeWhile (<4) [0..5]) because I have no idea whether any input will really result in success or failure until executed. 我不能使用@ Tom对mapM_ f (takeWhile (<4) [0..5])的建议,因为我不知道任何输入在执行之前是否真的会导致成功或失败。

I don't actually care about the returned list, either -- I just want to call the C functions until either the list is exhausted or the first C function returns a failure code. 我实际上并不关心返回的列表 - 我只想调用C函数,直到列表用完或第一个C函数返回失败代码。

In C-style pseudocode, my behavior would be: 在C风格的伪代码中,我的行为是:

 do { result = function_with_side_effects(input_list[index++]); } while (result == success && index < max_index); 

So again, @sth's answer performs the exact behavior that I want, except that the results may (should?) be discarded. 所以,@ sth的答案再次执行我想要的确切行为,除了结果可能(应该?)被丢弃。 A dropWhileM_ function would be equivalent for my purposes. 对于我的目的, dropWhileM_函数将是等效的。 Why isn't there a function like that or takeWhileM_ in Control.Monad? 为什么在Control.Monad中没有这样的函数或takeWhileM_ I see that there was a similar discussion on a mailing list , but it appears that nothing has come of that. 我看到在邮件列表上有类似的讨论 ,但似乎没有任何结果。

You might define sequence as 您可以将序列定义为

sequence xs = foldr (liftM2 (:)) (return []) xs

The problem with liftM2 that you've been seeing is you don't have an opportunity to stop m2 , which might be launchTheMissiles ! 你一直看到的liftM2的问题是你没有机会停止m2 ,这可能是launchTheMissiles

liftM2 :: (Monad m) => (a -> b -> c) -> m a -> m b -> m c
liftM2 f m1 m2 = do
    x1 <- m1
    x2 <- m2
    return (f x1 x2)

Using guard as in the following seems appealing: 如下所示使用guard似乎很有吸引力:

sequenceUntil p xs = foldr (myLiftM2 p (:)) (return []) xs
  where myLiftM2 p f m1 m2 = do
            x1 <- m1
            guard $ p x1
            x2 <- m2
            return (f x1 x2)

The code above will fail in your application because the IO monad is not an instance of MonadPlus . 上面的代码将在您的应用程序中失败,因为IO monad不是MonadPlus的实例。

So hold its hand a little more 所以握住它的手多一点

module Main where

import Control.Monad

printx :: Int -> IO Int
printx x = do
    print x
    return x

sequenceUntil :: (Monad m) => (a -> Bool) -> [m a] -> m [a]
sequenceUntil p xs = foldr (myLiftM2 (:) []) (return []) xs
  where myLiftM2 f z m1 m2 = do
            x1 <- m1
            if p x1 then do x2 <- m2
                            return $ f x1 x2
                    else return z

main :: IO ()
main = do
  let as :: [IO Int]
      as = map printx [1..10]
  ys <- sequenceUntil (< 4) as
  print ys

Even though as is a list of actions over 1 to 10, the output is 尽管as是行动在1到10的名单,产量

1
2
3
4
[1,2,3]

Discarding the results is then trivial: 丢弃结果是微不足道的:

sequenceUntil_ :: (Monad m) => (a -> Bool) -> [m a] -> m ()
sequenceUntil_ p xs = sequenceUntil p xs >> return ()

main :: IO ()
main = do
  let as :: [IO Int]
      as = map printx [1..]
  sequenceUntil_ (< 4) as

Note the use of [1..] that shows the new combinator maintains laziness . 注意使用[1..]表示新的组合子保持懒惰


You may prefer spanM : 您可能更喜欢spanM

spanM :: (Monad m) => (a -> Bool) -> [m a] -> m ([a], [m a])
spanM _ [] = return ([], [])
spanM p (a:as) = do
  x <- a
  if p x then do (xs,bs) <- spanM p as
                 return (x:xs, bs)
         else return ([x], as)

Note that it differs slightly from span in that it includes the failing element in the result list. 请注意,它与span略有不同,因为它在结果列表中包含失败元素。 The pair's second is the remaining actions. 这对是剩下的动作。 For example: 例如:

*Main> (xs,bs) <- spanM (< 4) as
1
2
3
4
*Main> xs  
[1,2,3,4]
*Main> sequence bs
5
6
7
8
9
10
[5,6,7,8,9,10]

Yet another alternative: 另一种选择:

untilM :: Monad m => (a -> Bool) -> [m a] -> m ()
untilM p (x:xs) = do
  y <- x
  unless (p y) $ untilM p xs

Note that the sense of the predicate is complemented: 请注意,谓词的意义是补充:

*Main> untilM (>= 4) as
1
2
3
4

I don't think there is anything like a takeWhileM in the standard library, but you could write it yourself so that only as much IO as needed is executed: 我认为标准库中没有类似takeWhileM东西,但您可以自己编写,以便只执行所需的IO:

takeWhileM :: (Monad m) => (a -> Bool) -> [m a] -> m [a]
takeWhileM _ [] = return []
takeWhileM p (a:as) =
   do v <- a
      if p v
         then do vs <- takeWhileM p as
                 return (v:vs)
         else return []

The supplied list is only evaluated until an element is found, that doesn't match the predicate: 提供的列表仅在找到与谓词不匹配的元素之前进行求值:

*Main> takeWhileM (<4) (map f [1..5])
1
2
3
4
[1,2,3]

Edit: Now I see what you're looking for. 编辑:现在我看到你在寻找什么。

gbacon posted a nice sequenceWhile function, which is almost the "primitive" you need. gbacon发布了一个很好的sequenceWhile函数,这几乎就是你需要的“原始”。

Actually, since you're only interested in the side effects, sequenceWhile_ should be enough. 实际上,既然你只对副作用感兴趣,那么sequenceWhile_就足够了。 Here's a definition (again, inspired by gbacon, vote him up!): 这是一个定义(再次受到gbacon的启发,投票给他!):

sequenceWhile_ :: (Monad m) => (a -> Bool) -> [m a] -> m ()
sequenceWhile_ p xs = foldr (\mx my -> mx >>= \x -> when (p x) my)
                            (return ()) xs

You call this like so: 你这样称呼:

Prelude Control.Monad> sequenceWhile (<4) $ map f [1..]

Original answer: 原始答案:

You can't just "unlift" the values from the IO Monad for use with takeWile , but you can "lift" takeWhile for use within a Monad! 你不能只是“解除” IO Monad中的值以与takeWile一起使用,但是你可以“提升” takeWhile以便在Monad中使用!

The liftM function will take a function (a -> b) to a function (ma -> mb) , where m is a Monad. liftM函数将函数(a -> b)作为函数(ma -> mb) ,其中m是Monad。

(As a side note, you can find a function like this by searching for its type on Hoogle , in this case by searching for: Monad m => (a -> b) -> (ma -> mb) ) (作为旁注,你可以通过在Hoogle上搜索它的类型来找到这样的函数,在这种情况下通过搜索: Monad m => (a -> b) -> (ma -> mb)

With liftM you can do this: 使用liftM您可以这样做:

Prelude> :m + Control.Monad
Prelude Control.Monad> let f x = print x >> return x
Prelude Control.Monad> liftM (takeWhile (<4)) $ mapM f [0..5]
0
1
2
3
4
5
[0,1,2,3]

Now, this might not be what you wanted. 现在,这可能不是你想要的。 The mapM will apply the f function to the entire list in sequence, before returning a list. 在返回列表之前, mapM将按mapMf函数应用于整个列表。 That resulting list is then passed to the lifted takeWhile function. 然后将结果列表传递给提升的takeWhile函数。

If you want to stop printing after the third element, you'll have to stop calling print. 如果要在第三个元素后停止打印,则必须停止调用打印。 That means, don't apply f to such an element. 这意味着,不要将f应用于这样的元素。 So, you'll end up with something simple like: 所以,你最终会得到一些简单的东西:

Prelude> mapM_ f (takeWhile (<4) [0..5])

By the way, should you wonder why mapM will first print everything, before returning the list. 顺便说一句,如果你想知道为什么 mapM会在返回列表之前首先打印所有内容。 You can see this by replacing the functions with their definitions: 您可以通过将函数替换为其定义来查看:

mapM f [0..1]
=
sequence (map f [0..1])
=
sequence (f 0 : map f [1..1])
=
sequence (f 0 : f 1 : [])
=
sequence ((print 0 >> return 0) : f 1 : [])
= 
sequence ((print 0 >> return 0) : (print 1 >> return 1) : [])
=
do x  <- (print 0 >> return 0)
   xs <- (sequence ((print 1 >> return 1) : []))
   return (x:xs)
=
do x  <- (print 0 >> return 0)
   xs <- (do y  <- (print 1 >> return 1)
             ys <- sequence ([])
             return (y:ys))
   return (x:xs)
=
do x  <- (print 0 >> return 0)
   xs <- (do y  <- (print 1 >> return 1)
             ys <- return []
             return (y:ys))
   return (x:xs)
=
do x  <- (print 0 >> return 0)
   xs <- (do y <- (print 1 >> return 1)
             return (y:[]))
   return (x:xs)
=
do x  <- (print 0 >> return 0)
   xs <- (print 1 >> return (1:[]))
   return (x:xs)
=
do x <- (print 0 >> return 0)
   print 1
   return (x:1:[])
=
do print 0
   print 1
   return (0:1:[])

This process of replacing functions with their definitions is called equational reasoning . 将函数替换为其定义的过程称为等式推理

If I didn't make any mistakes, you can now (hopefully) see that mapM (using sequence ) first prints everything, and then returns a list. 如果我没有犯任何错误,你现在(希望)可以看到mapM (使用sequence )首先打印所有内容, 然后返回一个列表。

You can use the one from the "List" package. 您可以使用“List”包中的那个。

import Control.Monad.ListT (ListT)
import Data.List.Class (execute, fromList, joinM, takeWhile)
import Prelude hiding (takeWhile)

f x = print x >> return x
main =
  execute . takeWhile (< 4) .
  joinM $ fmap f (fromList [0..5] :: ListT IO Int)
  • fromList [0..5] creates a monadic list containing 0..5 which performs no monadic actions fromList [0..5]创建一个包含fromList [0..5]列表,该列表不执行任何monadic操作
  • fmap f to that list results in a ListT IO (IO Int) which still performs no monadic actions, just contains ones. fmap f到该列表会产生一个ListT IO (IO Int) ,它仍然不执行ListT IO (IO Int)动作,只包含一个。
  • joinM turns that into a ListT IO Int . joinM将其转换为ListT IO Int every contained action would get executed when the item is consumed and its result will be the value in the list. 当项目被消耗时,每个包含的动作都将被执行,其结果将是列表中的值。
  • takeWhile is generalized for any List . takeWhile适用于任何List Both [] and " Monad m => ListT m " are instances of List . []和“ Monad m => ListT m ”都是List实例。
  • execute consumes the monadic list, executing all its actions. execute使用monadic列表,执行其所有操作。
  • In case you are interested in the results you can use "toList :: List m => ma -> ItemM m [a]" (" ItemM (ListT IO) " is IO ). 如果您对结果感兴趣,可以使用"toList :: List m => ma -> ItemM m [a]" (“ ItemM (ListT IO) ”是IO )。 so in this case it's " toList :: ListT IO a -> IO [a] ". 所以在这种情况下它是“ toList :: ListT IO a -> IO [a] ”。 Better yet you can keep using higher-order functions such as scanl , etc to process the monadic list as it is being executed. 更好的是,您可以继续使用高阶函数(如scanl等)来处理正在执行的monadic列表。

最近,您可以使用单子 hackage,包括方便的功能,像takeWhileM,dropWhileM,deleteByM等等。

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

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