Home > Back-end >  Adding error handling to function - Haskell
Adding error handling to function - Haskell

Time:10-24

I am completely new to haskell and seen examples online of how to add error handling but I'm not sure how to incorporate it in my context. Below is an example of the code which works before trying to handle errors.

expr'::Parser Double
expr' = term' `chainl1'` addop

term'::Parser Double
term' = factor' `chainl1` mulop

chainl :: Parser a -> Parser (a -> a -> a) -> a -> Parser a
chainl p op a = (p `chainl1` op) <|> pure a

chainl1 ::Parser a -> Parser (a -> a -> a) -> Parser a
chainl1 p op = p >>= rest
    where 
        rest a = (do
            f <- op
            b <- p
            rest (f a b)) <|> pure a

addop, mulop :: Parser (Double -> Double -> Double)

I've since expanded this to let addop and mulop return error messages if something irregular is found. This causes the function definition to change to:

addop, mulop :: Parser (Either String (Double -> Double -> Double))

In other programming languages I would check if f <- op is a String and return the string. However I'm not sure how to go about this in Haskell. The idea is that this error message returns all the way back to term'. Hence its function definition also needs to change eventually. This is all in the attempt to build a Monadic Parser.

CodePudding user response:

If you're using parsec then you can make your code more general to work with the ParsecT monad transformer:

import Text.Parsec hiding (chainl1)
import Control.Monad.Trans.Class (lift)

expr' :: ParsecT String () (Either String) Double
expr' = term' `chainl1` addop

term' :: ParsecT String () (Either String) Double
term' = factor' `chainl1` mulop

factor' :: ParsecT String () (Either String) Double
factor' = read <$> many1 digit

chainl1 :: Monad m => ParsecT s u m a -> ParsecT s u m (a -> a -> a) -> ParsecT s u m a
chainl1 p op = p >>= rest
  where 
    rest a = (do
      f <- op
      b <- p
      rest (f a b))
        <|> pure a

addop, mulop :: ParsecT String () (Either String) (Double -> Double -> Double)
addop = ( ) <$ char ' ' <|> (-) <$ char '-'
mulop = ((*) <$ char '*' <* lift (Left "error")) <|> (/) <$ char '/' <|> (**) <$ char '^'

I don't know what kind of errors you would want to return, so I've just made an error if an '*' is encountered in the input.

You can run the parser like this:

ghci> runParserT (expr' <* eof) () "buffer" "1 2 3"
Right (Right 6.0)
ghci> runParserT (expr' <* eof) () "buffer" "1 2*3"
Left "error"

CodePudding user response:

The answer based on parsec implementation.

Actually the operator <|> is what you need. It handles any parsing errors. In expression a <|> b if the parser a fails then the parser b will be run (expect if the parser a consume some input before fails; for handle this case you can use combinator try like this: try a <|> b).

But if you want to handle error depending to the kind of error then you should do like @Noughtmare answered. But then I recomend you to do that:

  1. Define your type for errors. It will be bugless to handle errors.

    data MyError
        = ME_DivByZero
        | ...
    
  2. You can simplify type signature if you define type alias for your parser.

    type MyParser = ParsecT String () (Either MyError)
    

    Then signatires will look like this:

    expr' :: MyParser Double
    addop, mulop :: MyParser (Double -> Double -> Double)
    
  3. Use throwError to throw your errors and catchError to handle your errors, that will be more idiomatic. So it's look like this:

    f <- catchError op $ \case
        ME_DivByZero -> ...
        ME_... -> ...
        err -> throwError err -- rethrow error
    
  • Related