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?
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 when
s. It's not necessary, it just makes things a lot simpler and cleaner :)
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]
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With