RDZ is away next week.
We can restate the rule for evaluating let
more simply:
letvalx:t = vineend -->e{v/x}
Similarly, consider a call to an anonymous function:
(fn( id )=> e )( v ) --> e{v/id}
Remember that to find the value of a variable we just look textually (lexically) "out" from the piece of code where it is referenced, and find the first binding occurrence.
As someone pointed out last lecture, it’s not quite right to say that you look “up”, you really have to look “out”. Example: (again, don’t write code like this!!)
let val x =
(let val x = 3 in x+6 end)
in xend
You can think of the ML interpreter as having
a giant LET
statement before your code that gives lots of things their bindings. A decent
model of val
at top level is that it adds to this set of definitions.
val x = 27; E == let val x:int = 27 in Eend
So we can say
val triple = (fn(z:int) => 3 * z); triple(14) == let val triple:int->int = (fn(z:int) => 3 * z) in triple(14)end
To
start with, we could naturally assume that fun works as follows.
Consider an example:
let fun triple(z:int):int = 3 * z in triple(14) end
We will assume FOR THE MOMENT that this is shorthand for saying
let val triple:int->int = (fn(z:int)=> 3 * z) in triple(14) end
The RSM allows us to do fun things with Higher Order Procedures, like:
fun apply_twice (f:int->int, x:int):int = f (f (x)) val x = apply_twice(triple,4) (* 36 *)
By RSM, triple(triple(4)) = triple(4+4+4)= (4+4+4)+(4+4+4)+(4+4+4)
fun new_mul9(z:int):int = apply_twice(triple,z)
We can also do this using functions that return functions!
type base = inttype func = base -> base fun twice_func (f:func):func = fn (x:base) => f (f (x))
This function returns an anonymous procedure!
How do we use it?
val newer_mul9 = twice_func(triple); fun compose (f:func, g:func):func = fn (x:base) => f (g (x)) fun twice_func2(f:func):func = compose (f,f) val newest_mul9 = twice_func2(triple);
With this definition, though, recursion doesn’t work. Try this:
let val fact = fn(z:int):int => if (z = 1) then z else z * fact (z – 1) in fact(3) end
How can we fix this?
Our syntax will now include fun as well:
Expressions:
e ::= c (* constants *)
| id (* variables *)
| let d in e end (* let expressions *)
| (fun id(id1:t1):t2 = e) (* recursive functions *)
Declarations:
d ::= val id = e (* value declarations *)
| fun id(id1:t1):t2 = e (* function declarations *)
Rule #D2[fun declarations]: to evaluate a function declaration fun f(x:t):t' =e, return the substitution that maps f to the function expression fun f(x:t):t'=e.
eval_decl(fun f(x:t):t' = e) = [(f,fun f(x:t):t'=e)]
Rule #E8 [fun expressions]: to evaluate a recursive function expression (fun f(x) = e), we "unroll" the function once. In other words, we return the anonymous function (fn (x) => e') where e' is the result of substituting (fun f(x) = e) for the identifier f within the body e. [I will drop types due to the annoying fact that fn’s syntax for return types is obscure, but it is easy to re-insert them]
eval(fun f(x) = e) = (fn (x) => e') where
substitute([f,(fun f(x) = e)],e) = e'.
Note that in the non-recursive case, this simplifies to the rule for fun we showed before (i.e., the call to substitute returns its second argument, so e’ = e). In other words, if f doesn’t appear in e (i.e. the function is not recursive) then the substitution does nothing and we turn fun into fn.
Consider:
eval(let fun fact(z) = if (z = 1) then z else z * fact (z – 1) in fact(3) end) We will let BODY = if (z = 1) then z else z * fact (z – 1)
By E7 Þ
eval((fun fact(z) = BODY)) (3)) By E8 Þ
eval((fn (z) => if (z = 1) then z else z * (fun fact(z) = BODY) (z – 1)) (3)) By E3 Þ
eval(if (3 = 1) then 3 else 3 * (fun fact(z) = BODY) (3 - 1))) By E6 Þ
eval(3*(fun fact(z) = BODY) (2))
As we can see, the evaluation model is not as trivial as we thought it was. Note that there end up being deferred evaluations here; we can’t multiply 3 * fact(2) until we know what fact(2) is. In fact (as it were), this allows us to give a precise definition of what it means for a function to be tail recursive: a function is tail-recursive if the size of the expression, using the RSM, does not depend on the input.
Now, try to use this new evaluation rule with other recursive funtions, like repeated.
fun repeated(f:func,n:int):func =
if (n=0)
then(fn(x:base) => x)
else compose (f, repeated(f,n-1))
This is a lot of fun (hah hah) to try in section…
We have now a precise semantics for our ML subset, we can use our semantic in different areas. In particular we use it to prove properties of programs, i.e. correctness and efficiency.
There are 2 key tools for this. The first one is our precise semantics; without this, we have no definition of what an ML program does, hence no hope of proving anything about it. Our semantics is precise enough so that one can prove things with arbitrary detail; it’s good to know how to do this, but you won’t be required to do this in the course.
The second key tool is, of course, induction. Almost all proofs in CS are based on induction.
We are going to prove a mathematical property of a program, with respect to a semantics. For example we might prove that fact(n) = n!. To say this with complete precision, what we are proving is:
For all integers n > 0,
eval((fun fact(z) = if (z = 1) then z else z * fact (z - 1)) (n)) = n!
This
is, to be precise, an infinite set of statements, one for each value of n.
Above, we started to prove it for n = 3. Not much fun for n = 106,
etc…
We will prove this statement by induction. This means that for all n Î Z+, we will prove that fact(n) = n! We’ll do this just once, rather than À0 times.
An induction proof has 4 parts:
We clearly state each the four steps
Now, to make life a little
easier we can introduce a shortcut and write BODY (in a box!) instead of if
(z = 1) then z else z * fact (z - 1). On homework or on the exams, if you do
this, we expect you to clearly state what BODY (in a box!) is.
Then we do the base case.
eval((fun fact(z) = BODY) (1)) Þ
eval((fn (z) => if (z = 1) then z else z * (fun fact(z) = BODY) (z – 1)) (1)) Þ
eval(if (1 = 1) then 1 else 1 * (fun fact(z) = BODY) (1 - 1))) Þ
1
Then we will continue proving the inductive step:
eval((fun fact(z) = BODY) (m+1)) Þ
eval((fn (z) => if (z = 1) then z else z * (fun fact(z) = BODY) (z – 1)) (m+1)) Þ
eval(if (m+1 = 1) then m+1 else (m+1) * (fun fact(z) = BODY)(m + 1 - 1))) Þ
We know that m Î Z+, then m >= 1 and m+1>1, therefore the if statemante results false and will evaluate the second expression.
eval((m+1) * (fun fact(z) = BODY) (m)) Þ
eval(eval(m+1) * eval((fun fact(z) = BODY)(m)))
Now we apply our Induction Hypothesis.
Þ
eval((m+1) * m!) Þ
(m+1)!