The second part of your case statement shouldn't try to retest because you already know it's not an Error
- that would have matched the first. (Skip /= Error err
.)
eval e1 = v1
tries to redo the eval you did at the start. You don't need to do that.
Here's what I think you intended to do:
eval :: Exp -> Error Val
eval (App e1 e2) = case eval e1 of
Error _ -> Error "Not an application"
S v1 -> case eval e2 of -- nested case statement
Error _ -> Error "Not an application"
S v2 -> appVals v1 v2 -- match the S away
But it all seems a bit ugly, so let's take exellent advice from Gabriel Gonzalez and make an applicative out of Error
.
instance Functor Error where
fmap f (Error e) = Error e -- pass through errors
fmap f (S x) = S (f x) -- edit successes
So for example, fmap (+4) (Error "oops") = Error "oops"
whereas fmap (+4) (S 5) = S 9
.
If this fmap
is all new to you, why not read a Functors tutorial?
Next let's make an Applicative instance. Applicative lets you use complicated functions like simple ones.
You need to import Control.Applicative
at the top of your file to get it working.
instance Applicative Error where
pure x = S x -- how to put ordinary data in
S f <*> S x = S (f x)
Error e <*> _ = Error e
_ <*> Error e = Error e
Now, if there weren't any errors then you'd define
appVal' :: Val -> Val -> Val
eval' :: Exp -> Val
eval' (App e1 e2) = appVal' (eval' e1) (eval' e2)
With applicative, we can use <$>
which works a bit like $
except it does whatever plumbing you defined in fmap
.
Similarly, <*>
works a bit like
function application, except for the extra plumbing, so we can define
eval :: Exp -> Error Val
eval (App e1 e2) = appVals <$> eval e1 <*> eval e2
which is a nice clean way of dealing with the errors behind the scenes whilst focussing on the functionality.