3

Is it possible to break out of a monad sequence?

For instance, if I want to break out of a sequence earlier based on some condition calculated in the middle of the sequence. Say, in a 'do' notation I bind a value and based on the value I want to either finish the sequence or stop it. Is there something like a 'pass' function?

Thanks.

r.sendecky
  • 9,933
  • 9
  • 34
  • 62
  • 1
    You could raise an exception. Check out Control.Exception. –  Jul 16 '13 at 13:34
  • What exactly do you mean by "monadic sequence" here? Do you mean a manually written `do` block, or perhaps some action that's looped with e.g. a `mapM_`? – leftaroundabout Jul 16 '13 at 13:50
  • @Michael Litchard That's exactly what I was thinking. If I stick something like error "I am out of here ..." in the middle of a sequence then it gives me the behavior I want. – r.sendecky Jul 16 '13 at 14:01
  • @leftaroundabout Yes, say in a 'do' block I want to exit earlier. Like the effect of putting error function in the middle. – r.sendecky Jul 16 '13 at 14:02
  • sweet! I wasn't sure what you wanted, but an exception seemed likely. –  Jul 16 '13 at 14:04
  • @AndrewC No problem, I'll fix it up in a sec. I just did not consider my edit as an answer - just something you guys can comment on. )) – r.sendecky Jul 16 '13 at 15:18
  • A nice way of breaking out of a sequence or loop using ErrorT/EitherT is explained here: http://www.haskellforall.com/2012/07/breaking-from-loop.html – danidiaz Jul 16 '13 at 15:27

4 Answers4

8

Directly using if

You could do this directly as Ingo beautifully encapsulated, or equivalently for example

    breakOut :: a -> m (Either MyErrorType MyGoodResultType)
    breakOut x = do
        y <- dosomethingWith x
        z <- doSomethingElseWith x y
        if isNoGood z then return (Left (someerror z)) else do
            w <- process z
            v <- munge x y z
            u <- fiddleWith w v
            return (Right (greatResultsFrom u z))

This is good for simply doing something different based on what values you have.

Using Exceptions in the IO monad

You could use Control.Exception as Michael Litchard correctly pointed out. It has tons of error-handling, control-flow altering stuff in it, and is worth reading if you want to do something complex with this.

This is great if your error production could happen anywhere and your code is complex. You can handle the errors at the top level, or at any level you like. It's very flexible and doesn't mess with your return types. It only works in the IO monad.

import Control.Exception

Really I should roll my own custom type, but I can't be bothered deriving Typable etc, so I'll hack it with the standard error function and a few strings. I feel quite guilty about that.

handleError :: ErrorCall -> IO Int
handleError (ErrorCall msg) = case msg of
   "TooBig" -> putStrLn "Error: argument was too big" >> return 10000
   "TooSmall" -> putStrLn "Error: argument was too big" >> return 1
   "Negative" -> putStrLn "Error: argument was too big" >> return (-1)
   "Weird" -> putStrLn "Error: erm, dunno what happened there, sorry." >> return 0

The error handler needs an explicit type to be used in catch. I've flipped the argument to make the do block come last.

exceptOut :: IO Int
exceptOut = flip catch handleError $ do
     x <- readLn
     if (x < 5) then error "TooSmall" else return ()
     y <- readLn
     return (50 + x + y)

Monad transformers etc

These are designed to work with any monad, not just IO. They have the same benefits as IO's exceptions, so are officially great, but you need to learn about monad tranformers. Use them if your monad is not IO, and you have complex requirements like I said for Control.Exception.

First, read Gabriel Conzalez's Breaking from a loop for using EitherT to do two different things depending on some condition arising, or MaybeT for just stopping right there in the event of a problem.

If you don't know anything about Monad Transformers, you can start with Martin Grabmüller's Monad Transformers Step by Step. It covers ErrorT. After that read Breaking from a Loop again!

You might also want to read Real World Haskell chapter 19, Error handling.

Call/CC

Continuation Passing Style's callCC is remarkably powerful, but perhaps too powerful, and certainly doesn't produce terribly easy-to-follow code. See this for a fairly positive take, and this for a very negative one.

mcmayer
  • 1,931
  • 12
  • 22
AndrewC
  • 32,300
  • 7
  • 79
  • 115
  • I am not sure it is beautiful, to be honest. And I usually prefer the explicit style you show there, therefore you got my +1. – Ingo Jul 16 '13 at 14:33
  • @Ingo :D I liked your combinator style as soon as I saw it, so I upvoted yours! – AndrewC Jul 16 '13 at 14:35
  • Still not sure if the x should better be on the end so that you might say `something >>= pass ifOk (do ...)` – Ingo Jul 16 '13 at 14:38
  • @Ingo that would be neat, yes. – AndrewC Jul 16 '13 at 15:02
  • @Ingo you _can_ use a do block if you put ` $ \x -> do ...`. Why not undelete? Combinators are nice. – AndrewC Jul 16 '13 at 15:56
  • 3
    This code could be made cleaner -especially the return parts- if it used the EitherT monad transformer. At the (potential) cost of having to supply a few lifts here and there. – danidiaz Jul 16 '13 at 16:11
  • @DanielDíazCarrete You're very right, but I've been [spending a lot of time writing](http://stackoverflow.com/a/17673690/1598537) on SO today, and so I've just put some pointers in. – AndrewC Jul 16 '13 at 17:25
2

So what I think you're looking for is the equivalent of return in imperative languages, eg

def do_something
  foo
  bar
  return baz if quux
  ...
end

Now in haskell this is doesn't work because a monadic chain is just one big function application. We have syntax that makes it look prettier but it could be written as

bind foo (bind bar (bind baz ...)))

and we can't just "stop" applying stuff in the middle. Luckily if you really need it there is an answer from the Cont monad. callCC. This is short for "call with current continuation" and generalizes the notation of returns. If you know Scheme, than this should be familiar.

import Control.Monad.Cont

foo = callCC $ \escape -> do
   foo
   bar
   when baz $ quux >>= escape
   ...

A runnable example shamelessly stolen from the documentation of Control.Monad.Cont

whatsYourName name =
  (`runCont` id) $ do
    response <- callCC $ \exit -> do
      validateName name exit
      return $ "Welcome, " ++ name ++ "!"
    return response

validateName name exit = do
  when (null name) (exit "You forgot to tell me your name!")

and of course, there is a Cont transformer, ContT (which is absolutely mind bending) that will let you layer this on IO or whatever.

As a sidenote, callCC is a plain old function and completely nonmagical, implementing it is a great challenge

daniel gratzer
  • 52,833
  • 11
  • 94
  • 134
1

So I suppose there is no way of doing it the way I imagined it originally, which is equivalent of a break function in an imperative loop.

But I still get the same effect below based in Ingo's answer, which is pretty easy (silly me)

doStuff x = if x > 5
            then do
                 t <- getTingFromOutside
                 doHeavyHalculations t
             else return ()

I don't know though how it would work if I need to test 't' in the example above ... I mean, if I need to test the bound value and make an if decision from there.

AndrewC
  • 32,300
  • 7
  • 79
  • 115
r.sendecky
  • 9,933
  • 9
  • 34
  • 62
  • That's fine - you can just do the same again, and add a line `if (t >1000) then return () else doHeavyCalculations t` instead of just `doheavyCalculations`. – AndrewC Jul 16 '13 at 15:28
0

You can never break out of a "monad sequence", by definition. Remember that a "monad sequence" is nothing else than one function applied to other values/functions. Even if a "monad sequence" gives you the illusion that you could programme imperative, this is not true (in Haskell)!

The only thing you can do is to return (). This solution of the practical problem has already been named in here. But remember: it gives you only the illusion of being able to break out of the monad!

H. Askell
  • 53
  • 5
  • 2
    Note: `return` doesn't do anything special like it would in imperative programming, it just turns its input as output. – AndrewC Jul 16 '13 at 15:05