代码之家  ›  专栏  ›  技术社区  ›  Mark Rushakoff

哈斯凯尔:一元茶点?

  •  12
  • Mark Rushakoff  · 技术社区  · 15 年前

    我有一些用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中包含的两个值。我知道这正是monads的目的——将结果链接在一起,并在满足某个条件时丢弃操作——但是在这种情况下,是否有一种简单的方法可以“包装”IO monad,在我选择的条件下停止执行链,而无需编写 MonadPlus ?

    我能把价值观从 f 为了Takewhile的目的?

    这是一个函数适合的解决方案吗?函数还没有“点击”我,但我有点印象,这可能是一个好的情况使用它们。


    更新:

    @某事物有最接近我想要的答案-事实上,这几乎正是我想要的,但我还是想看看是否有 标准 不是显式递归的解决方案——毕竟这是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_ 就我而言,函数是等价的。为什么没有这样的功能或者 takeWhileM_ 在控制之中。莫纳德?我看到了 a similar discussion on a mailing list 但似乎什么也没有发生。

    5 回复  |  直到 13 年前
        1
  •  15
  •   Greg Bacon    15 年前

    你可以定义 sequence 作为

    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..] 显示了新的组合器 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
    
        2
  •  14
  •   sth    15 年前

    我不认为有什么像 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
  •  10
  •   Tom Lokhorst    15 年前

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

    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) 到一个函数 (m a -> m b) 在哪里 m 是一个单子。

    (作为旁注,您可以通过在上搜索其类型来查找类似的函数 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]
    

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

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

        4
  •  6
  •   yairchu    15 年前

    您可以使用 "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 “是的实例 .
    • execute 使用一元列表,执行其所有操作。
    • 如果您对可以使用的结果感兴趣 "toList :: List m => m a -> ItemM m [a]" (“ ItemM (ListT IO) IO )所以在这种情况下 toList :: ListT IO a -> IO [a] “。更好的是,您可以继续使用高阶函数,例如 scanl 等处理正在执行的一元列表。
        5
  •  3
  •   shapr    13 年前

    最近,您可以使用 MonadList 包括 handy functions 像Takewhilem,Dropwhilem,DeleteBym等等。