繁体   English   中英

哈斯克尔:monadic takeWhile?

[英]Haskell: monadic takeWhile?

我有一些用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 “解除”值吗?

这是算子适合的解决方案吗? Functors还没有和我“点击”,但我觉得这可能是一个使用它们的好方法。


更新:

@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_ 我看到在邮件列表上有类似的讨论 ,但似乎没有任何结果。

您可以将序列定义为

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..]表示新的组合子保持懒惰


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

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

编辑:现在我看到你在寻找什么。

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)

使用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将按mapMf函数应用于整个列表。 然后将结果列表传递给提升的takeWhile函数。

如果要在第三个元素后停止打印,则必须停止调用打印。 这意味着,不要将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 )首先打印所有内容, 然后返回一个列表。

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

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

暂无
暂无

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

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