Haskell:单子 takeWhile?

发布于 2024-07-27 13:07:07 字数 1846 浏览 5 评论 0原文

我有一些用 C 编写的函数,我从 Haskell 调用它们。 这些函数返回 IO (CInt)。 有时我想运行所有函数,无论它们返回什么,这很容易。 为了示例代码,这是当前正在发生的事情的总体思路:

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

我得到了我想要的副作用,并且我不关心结果。 但现在我需要在第一个项目未返回我想要的结果后立即停止执行。 假设返回值 4 或更高需要停止执行 - 那么我想要做的是这样的:

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

这给了我这个错误:

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

这对我来说是有意义的 - 结果仍然包含在IO monad,我不能只比较 IO monad 中包含的两个值。 我知道这正是 monad 的目的——将结果链接在一起并在满足特定条件时丢弃操作——但是在这种情况下有没有一种简单的方法来“包装”IO monad 以停止根据条件执行链我的选择,而不编写 MonadPlus 的实例?

为了 takeWhile 的目的,我可以直接从 f 中“取消”值吗?

这是函子适合的解决方案吗? 函子还没有与我“合拍”,但我有这样的印象:这可能是使用它们的好情况。


Update:

@sth 有最接近我想要的答案 - 事实上,这几乎正是我想要的,但我仍然想看看是否有一个非显式递归的标准解决方案——毕竟这是 Haskell! 回顾我如何措辞我的问题,现在我发现我对自己想要的行为还不够清楚。

我上面用于示例的 f 函数只是一个示例。 真正的函数是用 C 编写的,并且专门用于其副作用。 我无法使用 @Tom 的 mapM_ f (takeWhile (<4) [0..5]) 的建议,因为我不知道任何输入在执行之前是否会真正导致成功或失败。

我实际上也不关心返回的列表——我只想调用 C 函数,直到列表耗尽或第一个 C 函数返回失败代码。

在 C 风格的伪代码中,我的行为将是:

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

所以,@sth 的答案再次执行我想要的确切行为,除了结果可能(应该?)被丢弃。 对于我的目的来说, dropWhileM_ 函数是等效的。 为什么 Control.Monad 中没有类似的函数或 takeWhileM_ ? 我看到邮件列表上有类似的讨论,但似乎没有任何结果。

I have some functions written in C that I call from Haskell. These functions return 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:

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. 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?

Can I just "unlift" the values from f, for the purposes of the takeWhile?

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.


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! 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. The real functions are written in C and used exclusively for their side effects. 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.

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.

In C-style pseudocode, my behavior would be:

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. A dropWhileM_ function would be equivalent for my purposes. Why isn't there a function like that or takeWhileM_ in Control.Monad? I see that there was a similar discussion on a mailing list, but it appears that nothing has come of that.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

野却迷人 2024-08-03 13:07:07

您可以定义序列 作为

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

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)

使用 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)

上面的代码将在您的应用程序中失败,因为 IO monad 不是 MonadPlus

因此,握紧它的手一点

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

即使 as 是超过 1 到 10 的操作列表,输出是

1
2
3
4
[1,2,3]

丢弃结果然后微不足道:

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

请注意 [1..]< 的使用/code> 显示新的组合器保持惰性


您可能更喜欢 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)

请注意,它与 span ,因为它在结果列表中包含失败的元素。 两人的第二个任务是剩下的行动。 例如:

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

另一种选择:

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

注意谓词的含义是补充的:

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

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 :: (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:

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.

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

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.


You may prefer 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. 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
魂归处 2024-08-03 13:07:07

我认为标准库中没有类似 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 []

仅对提供的​​列表进行评估,直到有一个元素出现为止。发现,与谓词不匹配:

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

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 :: (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]
兮子 2024-08-03 13:07:07

编辑:现在我明白你在寻找什么了。

gbacon 发布了一个很好的 sequenceWhile 函数,这几乎是您需要的“原始”函数。

实际上,由于您只对副作用感兴趣,因此 sequenceWhile_ 应该足够了。 这是一个定义(再次受到 gbacon 的启发,投票给他!):

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

您可以这样称呼:

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

原始答案:

您不能只是从 IO 中“取消”值 Monad 与 takeWile 一起使用,但您可以“提升”takeWhile 以在 Monad 中使用!

liftM 函数将将函数 (a -> b) 转换为函数 (ma -> mb),其中 m 是 Monad。

(顺便说一句,您可以通过在 Hoogle 上搜索其类型来找到这样的函数,在此案例通过搜索: Monad m => (a -> b) -> (ma -> mb)< /code>

使用 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]

现在,这可能不是你想要的。 在返回列表之前,mapM 会将 f 函数按顺序应用于整个列表。 然后将结果列表传递给提升的 takeWhile 函数。

如果您想在第三个元素之后停止打印,则必须停止调用 print。 这意味着,不要将 f 应用于此类元素。 因此,您最终会得到一些简单的结果,例如:

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

顺便说一下,您是否想知道为什么 mapM 将在返回列表之前首先打印所有内容。 您可以通过用函数的定义替换函数来看到这一点:

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

用函数的定义替换函数的过程称为等式推理。

如果我没有犯任何错误,您现在(希望)可以看到 mapM(使用 sequence)首先打印所有内容,然后然后返回一个列表。

Edit: Now I see what you're looking for.

gbacon posted a nice sequenceWhile function, which is almost the "primitive" you need.

Actually, since you're only interested in the side effects, sequenceWhile_ should be enough. Here's a definition (again, inspired by gbacon, vote him up!):

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!

The liftM function will take a function (a -> b) to a function (m a -> m b), where m is a 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) -> (m a -> m b))

With liftM you can do this:

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. That resulting list is then passed to the lifted takeWhile function.

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. 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. 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.

画尸师 2024-08-03 13:07:07

您可以使用 "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] 创建一个包含 0..5 的单子列表,该列表不执行单子操作
  • fmap f 到该列表会产生 ListT IO (IO Int) 仍然不执行单子操作,只包含单子​​操作。
  • joinM 将其转换为 ListT IO Int。 当该项目被消耗时,每个包含的操作都会被执行,其结果将是列表中的值。
  • takeWhile 泛化于任何List[] 和“Monad m => ListT m”都是List 的实例。
  • execute 使用单子列表,执行其所有操作。
  • 如果您对结果感兴趣,可以使用 "toList :: List m => ma -> ItemM m [a]" ("ItemM (ListT IO)”是IO)。 所以在本例中它是“toList :: ListT IO a -> IO [a]”。 更好的是,您可以继续使用高阶函数(例如 scanl 等)在执行时处理单子列表。

You can use the one from the "List" package.

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
  • fmap f to that list results in a ListT IO (IO Int) which still performs no monadic actions, just contains ones.
  • joinM turns that into a 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. Both [] and "Monad m => ListT m" are instances of List.
  • execute consumes the monadic list, executing all its actions.
  • In case you are interested in the results you can use "toList :: List m => m a -> ItemM m [a]" ("ItemM (ListT IO)" is IO). so in this case it's "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.
野稚 2024-08-03 13:07:07

最近,您可以使用包含 方便的函数,如 takeWhileM、dropWhileM、deleteByM 等等。

More recently, you can use the MonadList hackage that includes handy functions like takeWhileM, dropWhileM, deleteByM and many more.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文