Lecture 12: More Environments; Modifying the Evaluator

Administrivia

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)

Environment examples

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 = 39
in
  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!

Modifying the evaluator: Adding special forms

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.

Changes to the evaluator

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)
      in
        case (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 of
                 Bool_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 *)