Haskell: monadic takeWhile?

前端 未结 5 959
一整个雨季
一整个雨季 2020-12-30 07:33

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

5条回答
  •  北荒
    北荒 (楼主)
    2020-12-30 07:58

    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.

提交回复
热议问题