Breaking out of monad sequence

白昼怎懂夜的黑 提交于 2020-01-22 15:25:11

问题


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.


回答1:


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.




回答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




回答3:


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.




回答4:


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!



来源:https://stackoverflow.com/questions/17677910/breaking-out-of-monad-sequence

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!