non-negative int | lists | code | step |
---|---|---|---|
0 | [] | base case | base case |
n > 0 | x :: xs | inductive / recursive | inductive case |
(* length: int list -> int
REQUIRES: true
ENSURES:
*)
fun length ([] : int list): int = 0
| length (x :: xs) = 1 + length xs
This will result adding 1 AFTER recursive call finishes
(* tlength: (int list * int) -> int
REQUIRES: true
ENSURES: tlength (L, acc) ~= (length L ) + acc
*)
fun tlength ([] : int list, acc : int) = 0
| tlength (x::xs, acc) = tlength (xs, 1 + acc)
fun leng (L : int list) = tlength (L, 0)
Since the above function uses tail call
(meaning that no work after the recursive call, the final recursive call returns the answer), it conserves space
Tail Recursive: every recursive call is a trail call
If e1 \Rightarrow e2, then e1 \simeq e2 If e1 \hookrightarrow v2, then e1 \simeq e2 If e1 \Rightarrow e and e2 \Rightarrow e, then e1 \simeq e2
(Note that e1 \Rightarrow e or e2 \Rightarrow e may not be true even with e1 \simeq e2)
Function Simplification: [3/y, 5/2] (fn (x : int) x + y + z) ~= (fn (x:int) => x + 8)
If f
is total: f 1 + f 2
\simeq f 2 + f 1
Any exception + value is exception, therefore function are not associative in programming
total: for all expression applied to a function, the function reduce to a value valuable: expression reduce to a value without side-effects
What to induct:
variable that has base case
variable that is reducing / not increasing Theorem: tlength (L, acc) ~= length L + acc (for all values L : int list, acc : int) Proof: by structural induction on L Base Case: L = [] WTS tlength ([], acc) ~= length [] + acc (for all values acc : int)
tlength ([], acc) => acc [1st clause of tlength]
length [] + acc
=> 0 + acc [1st clause of length]
=> acc [math]
therefore, tlength([], acc) ~= length [] + acc
Inductive Step: L = x :: xs IH: tlength (xs, acc') ~= length xs + acc' (for any valuable acc') WTS tlength (x::xs, acc) ~= length (x::xs) + acc (for some fixed acc) tlength (x::xs, acc) ~= tlength (xs, 1 + acc) [2nd clause of tlength] ~= length xs + (1 + acc) [IH using acc' = 1 + acc, since we assume acc is valuable] TODO: is this assumption necessary? ~= (1 + length xs) + acc [math, since length is total] ~= length (x::xs) + acc
(* append: int list * int list -> int list
example: append ([1, 2], [3, 4]) => [1, 2, 3, 4]
ruuning time: O(length A)
*)
fun append ([]: int list, B: int list) : int list = B
| append (x:xs, B) = x :: append (xs, B)
This is built in append function that is right associative (infix form = @
), meaning everything in the left will be appended one by one to the right
(* rev: int list -> int list
*)
fun rev ([]: int list): int list = []
| rev (x :: xs) = rev xs @ [x]
This reverse function is bad because it has run time O(n^2)
(* 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)
Theorem: trev (L, acc) ~= (rev L) @ acc (for all values L, valuable acc) Proof: by structural induction on L Base Case: L = [] WTS trev ([], acc) ~= rev [] @ acc trev ([], acc) ~= acc [1st clause of trev] ~= [] @ acc [append] ~= rev [] @ acc [1st clause of rev] TODO: see induction step on Piazza
TODO: is there a difference between value and valuable? (coding level difference? proof level difference?)
Table of Content