As an environment I’ve chosen Dict
because it’s easy to extend and lookup items in Dict.
What can be done with this interpreter
Variable lookup
> eval (Var "x") Dict.empty
Err ("Variable not found") : Value
Since environment is an empty Dict error is returned.
> eval (Var "x") (Dict.singleton "x" (Str "X"))
Str "X" : Value
When environment contains variable it’s value is returned.
Lambda functions
> eval (Lam "x" (Var "x")) Dict.empty
Cls "x" (Var "x") (Dict.fromList []) : Value
identity
function. In Elm that would be \x -> x
. When lambda is defined closure is created for it with current environment. So closure is a lambda with environment.
> eval (Lam "x" (Lam "y" (Var "x"))) Dict.empty
Cls "x" (Lam "y" (Var "x")) (Dict.fromList []) : Value
always
function. In Elm that would be \x y -> x
. Always return first argument ignoring second argument. Interesting that it’s also a true
function. When working with conditions we want branching. In case of true we choose first branch, in case of false we choose second branch. Implement false
yourself to see how it works.
Function application
> env = (Dict.singleton "z" (Str "Z value")) -- z = "Z value"
Dict.fromList [("z", ("Z value")] : Env -- "Z value" : String
> id = Lam "x" (Var "x") -- id = \x -> x
Lam "x" (Var "x") : Exp -- <function> : a -> a
> eval (App id (Var "z")) env -- id z
Str ("Z value") : Value -- "Z value" : String
Environment and identity
function were extracted to env
and id
accordingly. Sure enough when we applied variable z
to indentity
function we got value for variable z
back.
> env = (Dict.singleton "z" (Str "Z value")) -- z = "Z value"
Dict.fromList [("z", ("Z value")] : Env -- "Z value" : String
> true = Lam "x" (Lam "y" (Var "x")) -- true \x y -> x
Lam "x" (Lam "y" (Var "x")) : Exp -- <function> : a -> b -> a
> eval (App true (Var "z")) env -- true z
Cls "y" (Var "x") (Dict.fromList [("x",Str ("Z value")),("z",Str ("Z value"))]) : Value -- <function> : a -> String
> eval (App (App true (Var "z")) (Var "unknown")) env -- true z "unknown"
Str ("Z value") : Value -- "Z value" : String
Environment is the same, it contains only value for z
variable. true
function was extracted. Notice when we applied true
function to one variable z
we got back closure with extended environment. Because true
is a function of two arguments we need to pass one more argument to fully apply it. Notice how environment was extended. It contains original value for z
variable and also it contains now a value for x
variable inside closure. The value for x
variable equals to the value of z
variable because we applied z
variable to true
function. That’s called lexical scoping. Environment was extended immutably only for that created closure. Outside of the closure environment is still the same. Also notice that it does not matter what second argument is. In our case it was not even defined in the environment. It’s just ignored.