Reputation: 58725
At least I think that's what's going on.
Main.hs:
module Main (
main
) where
import Arithmetic
import Data.Maybe
import Data.Either
import Control.Monad.Error
testExpr :: Expr Float
testExpr =
(MultExpr "*"
(AddExpr "XXX"
(NumExpr 1)
(AddExpr "-"
(NumExpr 24)
(NumExpr 21)
)
)
(NumExpr 5)
)
main :: IO ()
main = do
putStrLn $ case eval testExpr of
Left msg -> "Error: " ++ msg
Right result -> show result
Arithmetic.hs:
{-# LANGUAGE GADTs #-}
module Arithmetic where
type Op = String
data Expr a where
NumExpr :: Float -> Expr Float
AddExpr :: Op -> Expr Float -> Expr Float -> Expr Float
MultExpr :: Op -> Expr Float -> Expr Float -> Expr Float
eval :: (Monad m) => Expr Float -> m Float
eval (NumExpr n) = return n
eval (AddExpr "+" e1 e2) = evalBin (+) e1 e2
eval (AddExpr "-" e1 e2) = evalBin (-) e1 e2
eval (AddExpr "%" e1 e2) = evalBin (%) e1 e2
eval (AddExpr _ _ _ ) = fail "Invalid operator. Expected +, - or %"
eval (MultExpr "*" e1 e2) = evalBin (*) e1 e2
eval (MultExpr "/" e1 e2) = evalBin (/) e1 e2
eval (MultExpr _ _ _ ) = fail "Invalid operator. Expected * or /"
evalBin :: (Monad m) => (Float -> Float -> Float) -> Expr Float -> Expr Float -> m Float
evalBin op e1 e2 = do
v1 <- eval e1
v2 <- eval e2
return $ op v1 v2
infixl 6 %
(%) :: Float -> Float -> Float
a % b = a - b * (fromIntegral $ floor (a / b))
But, when eval
fails, I get an error in IO, without the "Error: " string appended.
Upvotes: 1
Views: 193
Reputation: 40787
Ah, I see the problem now!
You are importing Control.Monad.Error, but using the Either
monad, whose fail
definition calls error
rather than returning Left
.
What you need to do is change eval testExpr
to runIdentity . runErrorT $ eval testExpr
. You'll need to import Data.Functor.Identity
.
In an old version of the mtl (monad transformer library), Either's fail
method did indeed return Left
. However, the problem is that this only allowed Either e
to be a monad when e
was an instance of the Error
class. I believe this was considered especially undesirable because fail
is generally thought to be a mistake; many people think that it should be moved out of the Monad typeclass.
You could of course opt for a different method of error handling entirely, but this is the closest analogue to what you already have that works with the newest versions of the libraries.
I would suggest you specialise your code in the Arithmetic module to use ErrorT
and throwError
directly; as a bonus, this will also let you catch the errors you throw within your interpreter.
You could also define your own error type, and in that case I suggest defining your own monad that uses Either:
newtype Eval a = Eval { runEval :: Either EvalError a }
deriving (Functor, Applicative, Monad)
evalError :: EvalError -> Eval a
evalError e = Eval (Left e)
Either's monad instance will work just fine here; the only thing that has changed is its definition of fail
. Note that you'll need the GeneralizedNewtypeDeriving
extension to derive those instances.
You could of course use String instead of EvalError here, but that offers no benefits over a simple ErrorT
; the advantage with using your own monad with a custom error type is that you don't have to define an instance of Error
, which would require defining a "catch-all" error value for noMsg
/strMsg
.
Upvotes: 1
Reputation: 47052
Which version of base
are you using? fail
is no longer defined to return a Left
in the latest version of the Either e
monad, instead it uses the default definition (which calls error
, which throws an exception that can only be caught in IO).
I don't know why this changed.
Upvotes: 3