Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I "continue" in a `Monad` loop?

Tags:

haskell

Often times I found myself in need of skipping the rest of the iteration (like continue in C) in Haskell:

forM_ [1..100] $ \ i ->
    a <- doSomeIO
    when (not $ isValid1 a) <skip_rest_of_the_iteration>
    b <- doSomeOtherIO a
    when (not $ isValid2 b) <skip_rest_of_the_iteration>
    ...

However, I failed to find an easy way to do so. The only way I am aware of is probably the Trans.Maybe, but is it necessary to use a monad transform to achieve something so trivial?

like image 226
xzhu Avatar asked Sep 26 '15 02:09

xzhu


3 Answers

Remember that loops like this in Haskell are not magic...they're just normal first-class things that you can write yourself.

For what it's worth, I don't think it's too useful to think of MaybeT as a Monad transformer. To me, MaybeT is just a newtype wrapper to give an alternative implementation of (>>=)...just like how you use Product, Sum, First, And, etc. to give alternative implementations of mappend and mempty.

Right now, (>>=) for you is IO a -> (a -> IO b) -> IO b. But it'd be more useful to have (>>=) here be IO (Maybe a) -> (a -> IO (Maybe b) -> IO (Maybe b). As soon as you get to the first action that returns a Nothing, it's really impossible to "bind" any further. That's exactly what MaybeT gives you. You also get a "custom instance" of guard, guard :: Bool -> IO (Maybe a), instead of guard :: IO a.

forM_ [1..100] $ \i -> runMaybeT $ do
  a <- lift doSomeIO
  guard (isValid1 a)
  b <- lift $ doSomeOtherIO a
  guard (isValid2 b)
  ...

and that's it :)

MaybeT is not magic either, and you can achieve basically the same effect by using nested whens. It's not necessary, it just makes things a lot simpler and cleaner :)

like image 129
Justin L. Avatar answered Oct 20 '22 02:10

Justin L.


Here's how you would do it using bare-bones recursion:

loop [] = return ()   -- done with the loop
loop (x:xs) =
  do a <- doSomeIO
     if ...a...
        then return ()  -- exit the loop
        else do -- continuing with the loop
                b <- doSomeMoreIO
                if ...b...
                   then return () -- exit the loop
                   else do -- continuing with the loop
                           ...
                           loop xs -- perform the next iteration

and then invoke it with:

loop [1..100]

You can tidy this up a bit with the when function from Control.Monad:

    loop [] = return ()
    loop (x:xs) =
      do a <- doSomeIO
         when (not ...a...) $ do
           b <- doSomeMoreIO
           when (not ...b...) $ do
             ...
             loop xs

There is also unless in Control.Monad which you might prefer to use.

Using @Ørjan Johansen 's helpful advice, here is an simple example:

import Control.Monad

loop [] = return ()
loop (x:xs) = do
  putStrLn $ "x = " ++ show x
  a <- getLine
  when (a /= "stop") $ do
  b <- getLine
  when (b /= "stop") $ do
  print $ "iteration: " ++ show x ++ ": a = " ++ a ++ " b = " ++ b
  loop xs

main = loop [1..3]
like image 26
ErikR Avatar answered Oct 20 '22 01:10

ErikR


If you want to loop over a list or other container to perform actions and/or produce a summary value, and you're finding the usual convenience tools like for_ and foldM aren't good enough for the job, you might want to consider foldr, which is plenty strong enough for the job. When you're not really looping over a container, you can use plain old recursion or pull in something like https://hackage.haskell.org/package/loops or (for a very different flavor) https://hackage.haskell.org/package/machines or perhaps https://hackage.haskell.org/package/pipes.

like image 28
dfeuer Avatar answered Oct 20 '22 02:10

dfeuer