views:

274

answers:

6

What is a good way for a Haskell function to check a number of different conditions and return an error message on a failure?

In Python or similar language, it would be straightforward:

if failure_1:
    return "test1 failed"
if failure_2:
    return "test2 failed"
...
if failure_n:
    return "testn failed"
do_computation

How do you do this without arbitrarily nested case/if statements in Haskell?

Edit: some of the test conditions may require IO which puts any test results in the IO monad. I believe this puts a kink in a number of solutions.

A: 
f :: ErrorType -> Input -> Output
f failure_1 _ = error "test1 failed"
f failure_2 _ = error "test2 failed"
...
f _ args = do_computation args
KennyTM
+4  A: 

Generally pattern matching is a much better way to go than lots of if statements, and checking error conditions is no exception:

func :: [Int] -> Either String Int
func []    = Left "Empty lists are bad"
func [x]
   | x < 0 = Left "Negative? Really?"
   | odd x = Left "Try an even number"
func xs    = Right (length xs)

This function returns either an error message or the length of the parameter. The error cases are tried first and only if none of them match the last case is executed.

sth
Thanks It's a good idea. What I did not mention originally is that some of my test conditions need IO. Is there a way to check for IO condtions in a pattern guard?
me2
A: 

Use guards:

f z
  | failure_1 = ...
  | failure_2 = ...
  | failure_3 = ...
  | failure_4 = ...
  | otherwise = do_computation
Claudiu
+1  A: 

I don't think you can use IO in a guard.

Instead, you could do something like this:

myIoAction filename = foldr ($) [noFile, fileTooLarge, notOnlyFile] do_computation
  where do_computation
          = do {- do something -}
               return (Right answer)
        noFile success
          = do {- find out whether file exists -}
               if {- file exists -} then success else return (Left "no file!")
        fileTooLarge success
          = do {- find out size of file -}
               if maxFileSize < fileSize then return (Left "file too large") else success
        -- etc
Dave Hinton
+1  A: 

Taking your other question as an intended modification on this one, you could create something like a switch/case statement

select :: Monad m => [(m Bool, m a)] -> m a -> m a
select fallback [] = fallback
select fallback ((check, action) : others) = do
    ok <- check
    if ok then action else select fallback others

newfile :: FilePath -> IO Bool
newfile x = select
    (return True)
    [ (return $ length x <= 0, return False)
    , (doesFileExist x,        return False) ]

although this particular one could easily be written

newFile [] = return False
newFile fn = fmap not $ doesFileExist fn
ephemient
+3  A: 

So, you're stuck inside IO, and you want to check a bunch of conditions without lots of nested ifs. I hope you'll forgive me a digression onto more general problem solving in Haskell by way of answering.

Consider in abstract how this needs to behave. Checking a condition has one of two outcomes:

  • Success, in which case the program runs the rest of the function
  • Failure, in which case the program discards the rest of the function and returns the error message.

Checking multiple conditions can be viewed recursively; each time it runs "the rest of the function" it hits the next condition, until reaching the final step which just returns the result. Now, as a first step to solving the problem, let's break things apart using that structure--so basically, we want to turn a bunch of arbitrary conditions into pieces that we can compose together into a multi-conditional function. What can we conclude about the nature of these pieces?

1) Each piece can return one of two different types; an error message, or the result of the next step.

2) Each piece must decide whether to run the next step, so when combining steps we need to give it the function representing the next step as an argument.

3) Since each piece expects to be given the next step, to preserve uniform structure we need a way to convert the final, unconditional step into something that looks the same as a conditional step.

The first requirement obviously suggests we'll want a type like Either String a for our results. Now we need a combining function to fit the second requirement, and a wrapping function to fit the third. Additionally, when combining steps, we may want to have access to data from a previous step (say, validating two different inputs, then checking if they're equal), so each step will need to take the previous step's result as an argument.

So, calling the type of each step err a as a shorthand, what types might the other functions have?

combineSteps :: err a -> (a -> err b) -> err b
wrapFinalStep :: a -> err a

Well now, those type signatures look strangely familiar, don't they?

This general strategy of "run a computation that can fail early with an error message" indeed lends itself to a monadic implementation; and in fact the mtl package already has one. More importantly for this case, it also has a monad transformer, which means that you can add the error monad structure onto another monad--such as IO.

So, we can just import the module, make a type synonym to wrap IO up in a warm fuzzy ErrorT, and away you go:

import Control.Monad.Error

type EIO a = ErrorT String IO a

assert pred err = if pred then return () else throwError err

askUser prompt = do
    liftIO $ putStr prompt
    liftIO getLine

main :: IO (Either String ())
main = runErrorT test

test :: EIO ()
test = do
    x1 <- askUser "Please enter anything but the number 5: "
    assert (x1 /= "5") "Entered 5"
    x2 <- askUser "Please enter a capital letter Z: "
    assert (x2 == "Z") "Didn't enter Z"
    x3 <- askUser "Please enter the same thing you entered for the first question: "
    assert (x3 == x1) $ "Didn't enter " ++ x1
    return () -- superfluous, here to make the final result more explicit

The result of running test, as you would expect, is either Right () for success, or Left String for failure, where the String is the appropriate message; and if an assert returns failure, none of the following actions will be performed.

For testing the result of IO actions you may find it easiest to write a helper function similar to assert that instead takes an argument of IO Bool, or some other approach.

Also note the use of liftIO to convert IO actions into values in EIO, and runErrorT to run an EIO action and return the Either String a value with the overall result. You can read up on monad transformers if you want more detail.

camccann
Very nice answer!
Tom Lokhorst
I love the error monad! Also great for squelching spurious error messages in compilers.... +1
Norman Ramsey
@Norman Ramsey: Quite so! In my early adventures with Haskell, upon meeting libraries using `Either` for errors, I quickly became annoyed with the clumsiness of extracting from `Right` to pass values along; so I wrote functions that (in hindsight) were nearly equivalent to `fmap` and `(>>=)`. Around the same time that I gained the understanding to recognize the monadic structure, I discovered that `MonadError` existed all along, and felt somewhat silly for my crude reinventions. In all the fuss over "fancy" monads, it seems the elegance of monadic `Either` and `Maybe` gets forgotten...
camccann
Hello folks. I've been trying this method out but I am not liking it. In an IO () function that has a lot of IO, using ErrorT basically means polluting the function with a large number of liftIO's, which quickly becomes unwieldly. There is no more elegant way?
me2
Well, if you use a few particular functions a lot, you could write a wrapper around them, e.g. `liftedPutStr = liftIO . putStr`. You could even import the originals qualified and make your lifted version use the same name, if you wanted. Also, a group of IO actions that won't raise errors can be pulled out into a single, separate function that can then be `liftIO`d just once. Does that help?
camccann