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 zLet's try it:
3+ifnot(1 > 2, 39, 42) � 42So 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.
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 unbounded values to substitute, for instance
letsubst(x,z+3,let z:int = 39 in x end) � let z:int = 39 in z+3 end � 42If we try to use a SML let special form, we cannot do this:
let val x = z + 3 in let z:int = 39 in x endIf
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.
A critical function to consider is evaluate(ex: exp, en: env): value * typ
fun evaluate (ex: exp, en: env): value * typ = case ex of Int_c i => (Int_v i, Int_t) | Real_c r => (Real_v r, Real_t) | Bool_c b => (Bool_v b, Bool_t) | Char_c c => (Char_v c, Char_t) | String_c s => (String_v s, String_t) | Id_e id => (case lookupBinding (id, en) of NONE => err ("unbound variable " ^ id) | SOME v => v) | If_e (test, e1, e2) => (case evaluate (test, en) of (Bool_v b, Bool_t)=> evaluate(if b then e1 else e2, en) | _ => err ("'If' condition must be boolean.")) | Let_e (dlist, ex) => evaluate (ex, evaluateDeclare (dlist, en)) | Apply_e (e1, e2) => evaluateApply (e1, e2, en) | Unop_e (uop, ex) => evaluateUnop (uop, evaluate (ex, en)) | Binop_e (e1, bop, e2) => evaluateBinop (bop, evaluate (e1, en), evaluate (e2, en)) | Tuple_e elist => let val (v, t) = foldr (fn ((v, t), (vl, tl)) => (v::vl, t::tl)) ([],[]) (map (fn(e) => evaluate (e, en)) elist) in (Tuple_v v, Tuple_t t) end | Ith_e (i, ex) => (case evaluate (ex, en) of (Tuple_v vs, Tuple_t tlst) => (List.nth (vs, i), List.nth (tlst, i)) | _ => err "Projection from non-tuple.") | List_e elist => let val (v, t) = foldr (fn ((v, t), (vl, tl)) => (v::vl, t::tl)) ([],[]) (map (fn(e) => evaluate (e, en)) elist) in (* The empty list has a dummy type that can later be instantiated using context information. *) if null(v) then (List_v [], List_t (Undef_t)) else (List_v v, List_t (hd t)) end | Fn_e (args, t, body) => (Fn_v(args, en, body, NONE), t)
An exp is the representation of an expression, returned by the parser. The top-level REPL loops calling evaluate in a special enviroment (env) called empty, which contains a number of special bindings for various builtin functions (hd, tl, null, implode, explode), as well as the names of all special forms. An environment is represented as a list of bindings, each of which has a name, a value and a type spec (saying what it returns).
(If you type declarations into the REPL, it adds these to "empty", but there is no way to add new built-ins or special forms to the evaluator on the fly. Maybe someday?)
evaluate simply does a dispatch on the type of its argument, using the rules of evaluation.
To add special forms, we need to look further into the evaluator. Here are some changes that you could try to do in the evaluator. Originally we have evaluateApply
defined as follows:
and evaluateApply (e1: exp, e2: exp, en: env): value * typ = let val (fcn, fcnt) = evaluate (e1, en) val aa = evaluate (e2, en) in case fcn of Predef_v(name) => predefined(name, aa) | Fn_v([], en, body, name) => evaluate(body, (case name of NONE => en | SOME(s) => insertBinding(s, (fcn, fcnt), en))) ...
This function defines how the evaluator handles the application of functions and predefined values. Then to add the special forms, we could do the following changes to the evaluator:
update enviroment.sml: val empty = Env([ ("if3", SpecForm("if3"), Fn_t(Tuple_t([Bool_t, Undef_t, Undef_t]), Undef_t)), ("ifnot", SpecForm("ifnot"),Fn_t(Tuple_t([Bool_t, Undef_t, Undef_t]), Undef_t)), ("ifmaybe", SpecForm("ifmaybe"),Fn_t(Tuple_t([Undef_t, Undef_t]), Undef_t)), ("hd", Predef_v("hd"), Undef_t), ("tl", Predef_v("tl"), Undef_t), ("null", Predef_v("null"), Fn_t(List_t(Undef_t), Bool_t)), ("explode", Predef_v("explode"), Fn_t(String_t,Char_t)), ("implode", Predef_v("implode"), Fn_t(Char_t, String_t)) ]) update evaluator.sml: and evaluateApply (e1: exp, e2: exp, en: env): value * typ = let val (fcn, fcnt) = evaluate (e1, en) val aa = evaluate (e2, en) in case fcn of SpecForm(name) => specialForm(name, e2, en) |Predef_v(name) => predefined(name, aa) ... fun specialForm (name: string, e2: exp, en: env): value * typ = case name of (* If3 evaluates its first argument, which must result in a boolean value. If the result is true, the second argument is evaluated and returned. If the result is false, the third argument is evaluated and returned. Note that in the dynamic typechecking setting that we employ, it is not possible to determine whether the 'then' and 'else' branches return values of the same type without evaluating both. Thus in this version of Mini-SML we can legally write statements like this: >> if3(2 = 2, "true", 1/0) "true": string *) "if3" => (case e2 of Tuple_e([cond, thenE, elseE]) => (case evaluate(cond, en) of (Bool_v(true), Bool_t) => (* evaluate 'then' branch *) evaluate(thenE, en) | (Bool_v(false), Bool_t) => (* evaluate 'else' branch *) evaluate(elseE, en) | _ => err "first argument of if3 must be boolean") | _ => err "incorrect argument number for if3; should be 3") | "ifnot" => (case e2 of Tuple_e([cond, thenE, elseE]) => (case evaluate(cond, en) of (Bool_v(false), Bool_t) => (* evaluate 'then' branch *) evaluate(thenE, en) | (Bool_v(true), Bool_t) => (* evaluate 'else' branch *) evaluate(elseE, en) | _ => err "first argument of ifnot must be boolean") | _ => err "incorrect argument number for ifnot; should be 3") | "ifmaybe" => (case e2 of Tuple_e([thenE, elseE]) => (case flip() of 0 => (* evaluate 'then' branch *) evaluate(thenE, en) | 1 => (* evaluate 'else' branch *) evaluate(elseE, en) | _ => err "first argument of ifmaybe must be boolean") | _ => err "incorrect argument number for ifmaybe; should be 2") | _ => err "internal error [09]"
CS312 � 2002 Cornell University Computer Science |