Elegant casek / haskell handler in sequential monads

Since I previously simplified my other question , I would like to give a more striking example here.

How can I handle situations where I have to check certian conditions in a consistent way without inserting a few cases? With the "sequential way", I mean getting the value (for example, from stdin), checking this value for a certain condition and depending on the result receiving a different value, etc.

Example:

sequen :: IO String sequen = do a <- getLine case a of "hi" -> do putStrLn "hello!" b <- getLine case b of "how are you?" -> do putStrLn "fine, thanks" return "nice conversation" _ -> return "error 2" _ -> return "error 1" 

I know that there is a better way to write such a bot chat, it should just demonstrate the consistent nature of the problem. As you can see, with each nested case, the code also gets the indentation deeper.

Is there a better way to structure such code? I think of handling the “errors” in one place and describing the “path of success” without handling the errors distributed throughout it.

+6
source share
4 answers

Of course. That's what EitherT was created EitherT . You can get it from Control.Monad.Trans.Either in the EitherT package.

 import Control.Monad.Trans.Class import Control.Monad.Trans.Either main = do e <- runEitherT $ do a <- lift getLine case a of "hi" -> lift $ putStrLn "hello!" _ -> left 1 b <- lift getLine case b of "how are you?" -> lift $ putStrLn "fine, thanks!" _ -> left 2 return "nice conversation" case e of Left n -> putStrLn $ "Error - Code: " ++ show n Right str -> putStrLn $ "Success - String: " ++ str 

EitherT breaks the current block of code whenever it encounters a left statement, and people usually use this to indicate error conditions.

The internal block type is EitherT Int IO String . When you runEitherT , you will get IO (Either Int String) . The type left corresponds to the case when it worked with left , and the value Right means that it successfully reached the end of the block.

+20
source

I wrote a series of posts while returning to my own studies of the types Either and EitherT . You can read it here: http://watchchrislearn.com/blog/2013/12/01/working-entirely-in-eithert/

I use the errors package to get a bunch of nice helpers using the EitherT functions ( left and right , for example, to return canceled versions of left and right ).

By extracting your potential failure conditions into your own helpers, you can make the main line of your code read completely sequentially, without checking the results of case checks.

From this post you can see how the runEitherT section is a sequential piece of work, but simply has EitherT crash EitherT . Obviously, this code is pretty tricky to show how MaybeT plays inside EitherT . In real code, this will be just the story you wanted to tell, with one left / right at the end.

 import Control.Error import Control.Monad.Trans -- A type for my example functions to pass or fail on. data Flag = Pass | Error main :: IO () main = do putStrLn "Starting to do work:" result <- runEitherT $ do lift $ putStrLn "Give me the first input please:" initialText <- lift getLine x <- eitherFailure Error initialText lift $ putStrLn "Give me the second input please:" secondText <- lift getLine y <- eitherFailure Pass (secondText ++ x) noteT ("Failed the Maybe: " ++ y) $ maybeFailure Pass y case result of Left val -> putStrLn $ "Work Result: Failed\n " ++ val Right val -> putStrLn $ "Work Result: Passed\n " ++ val putStrLn "Ok, finished. Have a nice day" eitherFailure :: Monad m => Flag -> String -> EitherT String m String eitherFailure Pass val = right $ "-> Passed " ++ val eitherFailure Error val = left $ "-> Failed " ++ val maybeFailure :: Monad m => Flag -> String -> MaybeT m String maybeFailure Pass val = just $ "-> Passed maybe " ++ val maybeFailure Error _ = nothing 
+5
source

Since you are definitely in the IO monad, you are better off using the monad IO error handling capabilities instead of laying the monad error on top of the IO . He avoids all heavy lift ing:

 import Control.Monad ( unless ) import Control.Exception ( catch ) import Prelude hiding ( catch ) import System.IO.Error ( ioeGetErrorString ) main' = do a <- getLine unless (a == "hi") $ fail "error 1" putStrLn "hello!" b <- getLine unless (b == "how are you?") $ fail "error 2" putStrLn "fine, thanks" return "nice conversation" main = catch main' $ return . ioeGetErrorString 

In this case, your errors are simply String s, which are selected by IO fail , as userError . If you want to throw some other type, you will need to use throwIO instead of fail .

+1
source

Warning: a newbie from Haskell is responding.

You can avoid this kind of staircase with the Maybe monad. A good example at the beginning of this chapter.

However, you need something similar with monadic Either (presumably it is), since you are returning error codes.

The basic idea is that after you get the "Left 1" error, you will lock up any future steps (due to lazy evaluation).

0
source

All Articles