NuNu
NuNu

Reputation: 677

Haskell Switch/Case Use

As part of a mini interpreter that I'm writing in Haskell, I'm writing a function that does the following: In case of eval (App e1 e2), I want to recursively evaluate e1 (eval e1), setting the result to v1. Then using Switch/Case, I want to check the pattern of the v1 and if it's not an Error then recursively evaluate e2 (eval e2) and setting that value to v2. Using these two values v1 and v2, then I apply another function (appVals) on those values.

eval :: Exp -> Error Val
eval (App e1 e2) = appVals v1 v2 where
    v1 = case (eval e1) of
        Error err -> Error "Not an application"
        /= Error err -> eval e1 = v1
    v2 = case (eval e2) of
        Error err -> Error "Not an application"
        /= Error err -> eval e2 = v2

I think I may have figured it out but I'm not entirely sure I've done the switch/case part correctly. Any ideas/suggests?

Upvotes: 5

Views: 11537

Answers (4)

dave4420
dave4420

Reputation: 47062

I left you yesterday (modulo renaming) with

eval (App e1 e2) = appVals <$> eval e1 <*> eval e2

This is almost what you want, the difference being that if either of the two inputs is an error, you want to replace it with a specific error message. (What I don't understand is why.) So let's write a function to do just that last bit!

butError :: String -> Error a -> Error a
butError message (Error _) = Error message
butError _       noError   = noError

Now you can rewrite your eval clause as

eval (App e1 e2) = appVals <$> butError message (eval e1) <*> butError message (eval e2)
  where message = "Not an application"

Upvotes: 4

AndrewC
AndrewC

Reputation: 32455

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.

Upvotes: 12

Gabriella Gonzalez
Gabriella Gonzalez

Reputation: 35099

I assume that you have some data type that looks like:

data Result a = Error String | Success a

The solution to your problem is to either:

(a) Make your Result type a Monad

(b) Use Either String instead of result, since it already has a Monad instance.

Whichever you do, then you would just write:

eval :: SyntaxTree a -> Result a
eval (App ef ex) = do
    f <- eval ef
    x <- eval ex
    return (f x)

Technically, an Applicative suffices for this purpose, and you could instead write:

eval (App ef ex) = eval ef <*> eval ex

Upvotes: 3

Daniel Fischer
Daniel Fischer

Reputation: 183968

In a case, you need to have patterns for the alternatives, you can't have an /= Error err there.

Also, you cannot have a = on the right of an arrow -> in a case expression, so -> eval e1 = v1 is not valid Haskell.

In your case, you only care that it is not an Error something, but once the first pattern has not matched, that is automatically the case, and since you don't care what else you get, you can use a variable pattern:

eval (App e1 e2) = appVals v1 v2 where
    v1 = case (eval e1) of
        Error err -> Error "Not an application"
        other -> other
    v2 = case (eval e2) of
        Error err -> Error "Not an application"
        other -> other

Upvotes: 2

Related Questions