(* 15-150, Spring 2024 *)
(* Michael Erdmann & Dilsun Kaynar *)
(* Code for Lecture 4: Lists, Tail Recursion *)
(************************************************************************)
(* length : int list -> int
REQUIRES: true
ENSURES: length(L) returns the number of integers in L.
*)
fun length ([] : int list) : int = 0
| length (x::xs) = 1 + length(xs)
val 0 = length []
val 3 = length [1,4,8]
(* In lecture last time we used structural induction on lists to prove that
length is total, i.e., that
length(L) always reduces to a value when L is a value of type int list.
*)
(***********************************************************************)
(*
NOTATION:
In SML files, we use the double equal "=="
to mean "extensionally equivalent".
In LaTeX, please use the math symbol given by \cong
(\cong is also available as \eeq if you are using the
TA-provided template).
*)
(***********************************************************************)
(* And now with an accumulator argument: *)
(* tlength : int list * int -> int
REQUIRES: true
ENSURES: tlength(L, acc) == length(L) + acc
*)
fun tlength([] : int list, acc : int) : int = acc
| tlength(_::L, acc) = tlength(L, 1 + acc)
val 0 = tlength([], 0)
val 3 = tlength([1,4,8], 0)
(* length' : int list -> int
REQUIRES: true
ENSURES: length'(L) returns the number of integers in L.
*)
fun length' (L : int list) : int = tlength (L, 0)
val 0 = length' []
val 5 = length' [1,4,8,~2,1]
(* In class, we used structural induction on lists to prove that:
Theorem:
For all values L : int list and acc : int,
tlength(L, acc) == length(L) + acc.
The proof went like this:
Base Case: L = []
Need to show: For all values acc : int,
tlength([], acc) == length([]) + acc.
Showing:
Evaluating the left side:
tlength([], acc)
==> acc [1st clause of tlength]
Evaluating the right side:
length([]) + acc
==> 0 + acc [1st clause of length]
==> acc [SML's arithmetic]
Since the left and right expressions reduce to the same value,
the two expressions are extensionally equivalent.
Inductive Case: L = x::xs for some values x : int and xs : int list.
Induction Hypothesis: For all values acc' : int,
tlength(xs, acc') == length(xs) + acc'.
Need to show: For all values acc : int,
tlength(x::xs, acc) == length(x::xs) + acc.
Showing: tlength(x::xs, acc)
== tlength(xs, 1+acc) [step, 2nd clause of tlength]
== length(xs) + (1+acc) [by IH, with acc'=acc+1;
1+acc is a value since + is total]
== (1+length(xs)) + acc [+ is commutative and associative]
[totality of length is useful too]
== length(x::xs) + acc [step, 2nd clause of length]
By the principle of structural induction for lists, we have
proved the theorem.
Comment about the proof:
We proved the Base Case and the Inductive Case using two different
proof techniques merely to illustrate both techniques.
For the Base Case we evaluated the left and right hand sides
separately, whereas for the Inductive Case we used a sequence of
equivalences. In this example, we could have used either
technique for either part of the proof. Sometimes one technique
is easier than the other, so you should familiarize yourself with
both techniques.
*)
(*****************************************************************)
(* append : int list * int list -> int list
REQUIRES: true
ENSURES: append(L,R) evaluates to a list consisting of L followed by R
NOTE: this is also predefined in SML as the right-associative infix operator @.
*)
fun append ([] : int list, R : int list) : int list = R
| append (x::xs, R) = x::append(xs,R)
val [] : int list = append([],[])
val [1,2] = append([], [1,2])
val [1,2,5,6] = append([1,2], [5,6])
(* Direct implementation of a function to reverse a list: *)
(* rev : int list -> int list
REQUIRES: true
ENSURES: rev L returns the elements of L in reverse order.
*)
fun rev ([] : int list) : int list = []
| rev (x::xs) = (rev xs) @ [x]
val [] : int list = rev []
val [4,3,2,1] : int list = rev [1,2,3,4]
(* And now with an accumulator argument: *)
(* trev : int list * int list -> int list
REQUIRES: true
ENSURES: trev(L, acc) == (rev L) @ acc
*)
fun trev ([] : int list, acc : int list) : int list = acc
| trev (x::xs, acc) = trev(xs, x::acc)
val [9,10,11] : int list = trev([], [9,10,11])
val [4,3,2,1,0,0,7] : int list = trev([1,2,3,4], [0,0,7])
(* This next function has the same spec as the original rev function
but calls trev as a helper function.
*)
(* reverse : int list -> int list
REQUIRES: true
ENSURES: reverse(L) evaluates to the list L in reverse order.
*)
fun reverse (L : int list) : int list = trev(L, [])
val [] : int list = reverse []
val [4,3,2,1] : int list = reverse [1,2,3,4]
(* Exercise: Prove that rev and reverse are extensionally equivalent functions. *)
(************************************************************************)
(* Here are some simple examples of int -> int functions that are not total: *)
fun fact (0 : int) : int = 1
| fact (n) = n * fact(n-1)
(* fact is not total because it loops forever when called on negative
integers. Even if we specify via a REQUIRES that a user should only
call fact on nonnegative integers, the function itself is not total.
*)
(* Both of the following functions loop forever on all integer arguments: *)
fun f (x : int) : int = f(x)
fun g (x : int) : int = g(x)*0
(* Notice that f and g have different bodies but that
f(x) is extensionally equivalent to g(x) for all integer arguments x,
since both functions loop forever on all arguments.
We thus say that f is extensionally equivalent to g.
*)
(* Observe also that
(fact 1) + (7 div 0) == (7 div 0) + (fact 1).
(Recall that in code files we write "==" to mean "extensionally equivalent".)
However,
(fact ~1) + (7 div 0)
is NOT extensionally equivalent to
(7 div 0) + (fact ~1).
Why is that?
*)
(***************************************************************************)