Solutions to Selected Interpreter Problems CS312 - Fall 2004 Note: We have occasionally split Mini-ML code into several lines to improve legibility. In actuality, all mini-ML programs must be entered on a single line. Keep this is mind if you are testing the code shown below. P1-3. -------------------------------------------------- (a) Predefining a recursive function: We add the following lines to the ReadEvalPrint structure: open AbstractSyntaxTree (* Define global environment so that it contains recursive functions. *) fun makeGlobalEnv(): environment = let val body = IF_E(BINOP_E (VAR_E "n",EQ_B,INT_E 0), INT_E 1, BINOP_E (VAR_E "n", TIMES_B, APP_E (VAR_E "fact", BINOP_E (VAR_E "n", MINUS_B, INT_E 1)))) val hole = ref [] val env = env_add("fact", FN_V("n", body, hole), empty_env) val () = hole := env in env end We change line Evaluator.eval(ast, AbstractSyntaxTree.empty_env) in function evaluateString to Evaluator.eval(ast, makeGlobalEnv()). We redefine empty_typenv to be val empty_typenv = [("fact", FN_T(INT_T, INT_T)] rather than val empty_typenv = [] Sample output: MiniML> fact closure (arg name = n) MiniML> fact "5" Function argument has wrong type MiniML> MiniML> fact true Function argument has wrong type MiniML> MiniML> fact 3 6 MiniML> MiniML> fact 10 3628800 MiniML> MiniML> let val x = fact 5 in x + (fact 5) end 240 MiniML> A similar idea can be used to define two or more mutually recursive functions: just define the closures using the same dummy reference to an (empty) environment. After all closures have been added to the environment, all the references can be reset to a reference to the environment that contains all closures. P8. -------------------------------------------------- Replace the implementation of APP_E in function eval (file evaluator.sml) with the following code: ... | APP_E(e1, e2) => (case expToSpecialFormName e1 of SOME s => evaluateSpecialForm(s, e2, env) | NONE => (case (eval(e1, env), eval(e2, env)) of (EXCEPTION_V(x, s), _) => EXCEPTION_V(x, s) | (_, EXCEPTION_V(x, s)) => EXCEPTION_V(x, s) | (FN_V(x, body, renv), v2) => eval(body, env_add(x, v2, !renv)))) ... Note how both the function and its argument are evaluated unconditionally. P11. -------------------------------------------------- We implement special form ifmaybe. You will note that some of the infrastructure for this special form is already in place. For now, we will assume that type checking is disabled. First, we define the seed of the random number generator at the top of file evaluator.sml: val rndSeed = Random.rand (17, 91) We add the following code to function evaluateSpecialForm, replacing the current implementation that only raises the Fail exception: ... IF_MAYBE => (case expr of TUPLE_E [e1, e2] => eval(if (Random.randRange (0, 1) rndSeed) = 1 then e1 else e2, env) | _ => EXCEPTION_V("Runtime", "IF_MAYBE needs two arguments")) ... Here is a sample output: MiniML> ifmaybe("one", "two") "two" MiniML> ifmaybe("one", "two") "one" MiniML> ifmaybe("one", "two") "one" MiniML> ifmaybe("one", "two") "two" MiniML> ifmaybe("one", "two") "two" MiniML> ifmaybe("one", "two") "two" MiniML> ifmaybe("one", "two") "one" MiniML> ifmaybe("one", "two") "two" Note that the same mini-ML code has different outcomes, depending on the value returned by the random number generator. We do not pay too much attention to the random number generator to make it unbiased - here we focus on the language issues. Now, this implementation, since it does no true type checking, accepts expressions that clearly should not type-check: MiniML> ifmaybe("one", 2) "one" MiniML> ifmaybe("one", 2) "one" MiniML> ifmaybe("one", 2) "one" MiniML> ifmaybe("one", 2) 2 MiniML> ifmaybe("one", 2) 2 MiniML> ifmaybe("one", 2) "one" MiniML> ifmaybe("one", 2) 2 MiniML> ifmaybe("one", 2) "one" Depending on what we want, this might turn out to be acceptable, and it might even be seen as a desirable feature. We must point out, however, that having expressions whose type (as opposed to their value within a given type) depends on side effects (generating a random number is a side effect) is probably not a good idea in most cases. If type checking is implemented correctly (see P15 below), then type-checking already made sure that there are exactly two arguments of the same type to the special form. If this is not the case, it is not the user's fault, but the type-checker's. Thus we can modify our code as follows (look at the exception case): ... IF_MAYBE => (case expr of TUPLE_E [e1, e2] => eval(if (Random.randRange (0, 1) rndSeed) = 1 then e1 else e2, env) | _ => raise brokenTypes ... P15. -------------------------------------------------- Type checking for ifmaybe is not complicated, all we need to do is to make sure that the types of the two arguments to the special form are the same; this will also be the (return) type of the special form itself. We modify function typeCheckSpecialForm in file typechecker.sml by changing it so that the treatment of IF_MAYBE becomes the following: ... IF_MAYBE => (case expr of TUPLE_E [e1, e2] => type_unification(get_type(e1, tenv), get_type(e2, tenv), "args must be of the same type") | _ => raise StaticTypeError "ifmaybe takes exactly wo args") ... If you have just solved P11 above, you have probably disabled type-checking; re-enable it now. So let us try out type checking: MiniML> ifmaybe("one", "two") Type error: Variable ifmaybe does not have a defined type So what is happening? Special forms look like functions, and the AST does not, in fact, distinguish between a call to a function or to a special form. If you examine how APP_E is handled in evaluator.sml, you will notice how we test for special forms first, and "divert" those calls to function evaluateSpecialForm. We should have done the same with type checking. We thus need to modify function get_type in file typechecker.sml to incorporate the special treatment of special forms: ... let val specForm = case e1 of VAR_E x => (case SpecialForms.nameToSpecialFormName x of SOME t => SOME t | NONE => NONE) | _ => NONE in case specForm of SOME s => typeCheckSpecialForm(s, e2, tenv) | NONE => (case (get_type(e1, tenv), get_type(e2, tenv)) of (EXCEPTION_T, _) => EXCEPTION_T | (FN_T(ta, tb), EXCEPTION_T) => tb | (FN_T(ta, tb), t2) => (type_unification(ta, t2, "Arg to application has wrong type"); tb) | (_, _) => raise StaticTypeError "Function expected.") end ... With this change, type checking works as expected: MiniML> ifmaybe("one", "two") "two" MiniML> ifmaybe("one", 2) Type error: args must be of the same type MiniML> ifmaybe(raise Exception "first arg", true) true MiniML> ifmaybe(raise Exception "first arg", true) true MiniML> ifmaybe(raise Exception "first arg", true) exception Exception with message "first arg" MiniML> ifmaybe Type error: Identifier ifmaybe does not have a defined type Note especially the last example: the special form does not have a type when used in isolation (without being applied to arguments, that is). We could solve this problem by adding the type of various special forms to the initial (global) type environment (empty_typenv, a misnomer in this case). The problem is that many special forms do not have a definite type, for example ifmaybe has type 'a * 'a -> 'a, which can not be expressed in mini-ML. We could use type EXCEPTION_T to indicate an indiferent type (since EXCEPTION_T can be unified with any type), but we can not express the equality of otherwise indiferent types (i.e. something like EXCEPTION_T * EXCEPTION_T will match both int * int, int * string, and string * string, for example). Given our limited goals we will not pursue this matter further here. The limitation is not too important, however, since it only precludes the use of ifmaybe in situations that are not analogous to function applications, i.e. when ifmaybe is used in isolation. For example, ifmaybe can not be passed as the argument to another function: MiniML> let fun f(g: int -> int): int = g 3 in f (fn (n: int) => 7 + n) end 10 MiniML> let fun f(g: int -> int): int = g 3 in f ifmaybe end Type error: Identifier ifmaybe does not have a defined type It can even be argued that this limitation is in fact a useful feature that prevents the programmer to pass a special form as an argument instead of a true function.