Closures

Closures implement static scoping by including an environment variable in the function value.

Note

While this fixes static scoping, we cannot use recursion with closures.

data FBAE where
    Closure :: String -> FBAE -> Env -> FBAE

We also define a value type that is returned by the interpreter. This keeps the same values, but instead of defining lambda as a value, we define a closure as a value.

data FBAEVal where
  NumV :: Int -> FBAEVal
  ClosureV :: String -> FBAE -> Env -> FBAEVal -- This is new!

Within the interpreter, the only thing that really changes is the type we return and capture and what we return when evaluating a lambda.

eval :: Env -> FBAE -> Maybe FBAEVal
eval e (Num n) = NumV n
eval e (Plus l r) = do { (NumV l') <- eval e l;
                         (NumV r') <- eval e r;
                         return (NumV l'+r')
                        }
eval e (Lambda i s) = return (ClosureV i s e)
eval e (App f a) = do { (Closure i s e') <- eval e f;
                        a' <- eval e a; 
                        -- Eval using the closure's environment instead of the current environment.
                        eval (i,a'):e' s
                       }
Note

While while is done with a copy in this implementation, and most other functional implementations. This is commonly done in other languages by pointing to stack space.