首页 文章

哈斯克尔:monadic takeWhile?

提问于
浏览
12

我有一些用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 来自 f 的值吗?

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


Update:

@sth对我想要的答案最接近 - 实际上,'s almost exactly what I was going for, but I'仍然想知道是否有一个标准的解决方案对我理想的行为不够清楚 .

我上面用于示例的 f 函数仅仅是一个例子 . 实际功能是用C语言编写的,专门用于副作用 . 我可以 mapM_ f (takeWhile (<4) [0..5]) 建议 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_ ?我看到有a similar discussion on a mailing list,但似乎没有任何结果 .

5 回答

  • 15

    您可以将sequence定义为

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

    liftM2的问题,你've been seeing is you don'有机会停止 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..] 显示新的组合子maintains laziness .


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

    我不认为标准库中有 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]
    
  • 3

    Edit: 现在我看到你在找什么 .

    gbacon发布了一个很好的 sequenceWhile 函数,这几乎就是你需要的"primitive" .

    实际上,既然你只对副作用感兴趣,那么_3042630就足够了 . 这是一个定义(再次受到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..]
    

    Original answer:

    你不能只使用 IO Monad的"unlift"值与 takeWile 一起使用,但你可以在Monad中使用"lift" takeWhile

    liftM函数将函数 (a -> b) 带到函数 (m a -> m b) ,其中 m 是Monad .

    (作为旁注,您可以通过在Hoogle上搜索其类型来找到这样的函数,在这种情况下通过搜索:Monad m => (a -> b) -> (m a -> m b)

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

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

    如果要在第三个元素后停止打印,则'll have to stop calling print. That means, don' t将 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 )首先打印所有内容,然后返回一个列表 .

  • 6

    您可以使用"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的monadic列表,该列表不执行任何monadic操作

    • fmap f 到该列表导致 ListT IO (IO Int) 仍然不执行任何monadic操作,只包含一个 .

    • joinM 将其转换为 ListT IO Int . 当项目被消耗时,每个包含的动作都将被执行,其结果将是列表中的值 .

    • takeWhile 适用于任何 List . [] 和“ Monad m => ListT m ”都是 List 的实例 .

    • execute 使用monadic列表,执行其所有操作 .

    • 如果您对结果感兴趣,可以使用 "toList :: List m => m a -> ItemM m [a]" (“ ItemM (ListT IO) ”是 IO ) . 所以在这种情况下它是“ toList :: ListT IO a -> IO [a] ” . 更好的是,您可以继续使用高阶函数(如 scanl 等)来处理正在执行的monadic列表 .

  • 14

    最近,您可以使用包含handy functionsMonadList hackage,如takeWhileM,dropWhileM,deleteByM等等 .

相关问题