Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 615
NuNu Avatar asked Nov 30 '22 13:11

NuNu


2 Answers

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.

like image 81
AndrewC Avatar answered Dec 09 '22 11:12

AndrewC


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"
like image 24
dave4420 Avatar answered Dec 09 '22 11:12

dave4420