Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Breaking out of monad sequence

Tags:

haskell

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.

like image 941
r.sendecky Avatar asked Dec 15 '22 08:12

r.sendecky


2 Answers

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.

like image 53
AndrewC Avatar answered Jan 01 '23 09:01

AndrewC


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

like image 40
Daniel Gratzer Avatar answered Jan 01 '23 09:01

Daniel Gratzer