PS#3 is out, we hope you are hard at work on it.
Note: do not sort nested lists lexicographically as well.
We recommend that you use the existing RB tree code. Don’t delete!
Shadowing: can make new tree with a different value (easiest way)
As an example we could say [warning: sample prelim question ahead!]
let val myfun =
(let val x = 3 in fn(y) => x + y end) val x = 39in
myfun(x)end
When we apply a function (i.e. we see a combination) we evaluate the arguments then add them to the closure’s environment (then, we call eval on the body in this environment). It is extremely important to understand this, and well worth going through some examples!
One of the major limitations of ML is that it is impossible to add new special forms. What do I mean by a special form? A function that controls the evaluation of its arguments.
In ML, functions evaluate their arguments (be sure you know this fact). As a consequence, it is impossible to write a function ifnot such that
ifnot(x,y,z) = if (not x) then y else z
Why? Consider
fun ifnot(x:bool, y:int, z:int):int = if (not x) then y else z
Let's try it:
3+ifnot(1 > 2, 39, 42)
è42
So it works. But, what about ifnot(1 > 2, 25, raise Fail "can't get here")? the function will raise the exception regardless of the value of the boolean expression!. So in the SML implementation we have, we are stuck; there is no way to write a new special form. The special forms are fixed; we're stuck with the ones that happen to be built into the language. [Be sure you understand this by prelim #2!]
But what about Mini-ML? A different story! We can modify the interpreter by adding new special forms to it. Here are the examples we will focus on:
ifnot(e1,e2,e3)
To have that e2 will be evaluated only if e1 is false, and e3 will be evaluated if e1 is true.
ifmaybe(e1,e2) [flips a coin]
To have that e1 will be evaluated only if flip chooses e1, and e2 in the other case.
letsubst(var,e1,e2) [substitute e1 for var in e2]
To have possible unbound values to substitute, for instance
letsubst(x,z+3,let z = 39 in x end)
èlet z:int = 39 in z+3 end
è42
If we try to use a SML let special form, we cannot do this:
let val x = z + 3 in let z = 39 in x end
If z is not defined, this can't be evaluated. In the case that z has another bound value, for instance 10, then the answer will be 13, which is not what we wanted.
We need to change the evaluator structure to allow arguments not to be evaluated.
Old code:
fun evaluate (exp : exp, env : env) = case exp of…
| Apply_e (func, arg) => let val f = evaluate(func, env) val a = evaluate(arg, env) incase (f,a) of
(Fn_v _, Tuple_v alist) => apply(f,alist) | (Predef_v (name), Tuple_v alist) => applyPredef(name,alist)This is no good; we need to make this a separate function (we will call it evaluateApply) :
| Apply_e (func, arg) => evaluateApply(func, arg, env)
Note that it is easy to get the old behavior, we just have
fun evaluateApply (func: exp, arg: exp, env: env) =
let val f = evaluate(func, env) val a = evaluate(arg, env) in case (f,a) of (Fn_v _, Tuple_v alist) => apply(f,alist) | (Predef_v (name), Tuple_v alist) => applyPredef(name,alist)
This is always a useful sanity check when you introduce a new abstraction (it will catch various stupid errors...)
evaluateApply will support special forms, which will be stored in the environment. To do this we first need to modify the value data type:
datatype value =Int_v of int
| Predef_v of string (* name of predefined function *)
| SpecForm_v of string (* special form *) val topLevel = [("hd", Predef_v ("hd")),
…
("if3", SpecForm_v ("if3")),…
]fun evaluateApply (func: exp, arg: exp, env: env) =
let val f = evaluate(func, env) fun evalAsUsual() = let val a = evaluate(arg, env) in case (f,a) of (Fn_v _, Tuple_v alist) => apply(f,alist) | (Predef_v (name), Tuple_v alist) => applyPredef(name,alist) | _ => Error.runtime "attempted to apply non-function"end
fun evalSpecial(name: string) = case (name,arg) of ("if3", Tuple_e [arg1, arg2, arg3]) => (case evaluate(arg1,env) of Bool_v true => evaluate(arg2, env) | Bool_v false => evaluate(arg3, env) | _ => Error.runtime “first arg to if3 not boolean”) | _ => Error.runtime "no handler for this special form" in case f of SpecForm_v(name) => evalSpecial(name) | _ => evalAsUsual() end
Some consequences of the implementation:
val answer = 42;
val badtry = fn(a,b,c) => if a then b else c;
badtry(answer = 42, “truth”, 1/0); (* error *)
if3(answer = 42, “truth”, 1/0); (* “truth” *)
val goodtry = if3;
goodtry(answer = 42, “truth”, 1/0); (* “truth” *)
let val tryer = (if goodtry=if3 then if3 else badtry)
in
tryer(answer = 42, “truth”, 1/0) end
You should be sure you understand what parts of the implementation cause this behavior!
Also, what about this version:
fun evaluateApply (func: exp, arg: exp, env: env) =
let val f = evaluate(func, env) fun evalAsUsual() = let val a = evaluate(arg, env) in case (f,a) of (Fn_v _, Tuple_v alist) => apply(f,alist) | (Predef_v (name), Tuple_v alist) => applyPredef(name,alist) | _ => Error.runtime "attempted to apply non-function" end fun evalSpecial(name: string) = case (name,arg) of ("if3", Tuple_e [arg1, arg2, arg3]) => (case arg1 ofBool_e true => evaluate(arg2, env) (* not Bool_v *)
| Bool_e false => evaluate(arg3, env) (* not Bool_v *) | _ => Error.runtime “first arg to if3 not boolean”) | _ => Error.runtime "no handler for this special form" in case f of SpecForm_v(name) => evalSpecial(name) | _ => evalAsUsual() end
On to letsubst, which is now easy.
| "letsubst" => (case expr of Tuple_e([Id_e(varname), e1, e2]) => let val newval = evaluate(e1, env) val newenv = insertbinding(varname, newval, env) in evaluate(e2, newenv)(* Error checking omitted *)