Commit 03a88f9f by Alan Bleisch

### Update section4.ml so start is correct. Previously checked for even after...

`Update section4.ml so start is correct. Previously checked for even after seeing even, odd after seeing odd.`
parent 5c17afb5
 (* CSE 341, Section 4, Completed/Solution Code *) (* mutual recursion, just since we haven't shown it to you *) (* can do any finite state machine this way -- this one checks for strictly alternating even/odd mutual recursion is often not tail-recursive, but for finite state machines it is *) let rec start xs = match xs with | [] -> true | i::xs' -> if i mod 2 = 0 then saw_even xs' else saw_odd xs' and saw_even xs = match xs with | [] -> true | i::xs' -> i mod 2 = 0 && saw_odd xs' and saw_odd xs = match xs with | [] -> true | i::xs' -> i mod 2 <> 0 && saw_even xs' (* style above is correct, but we could if we really had to use first-class functions to encode mutual recursion by having earlier functions take function arguments and then calling them with later functions *) let saw_even2 f xs = match xs with | [] -> true | i::xs' -> i mod 2 <> 0 && f xs' let rec saw_odd2 xs = match xs with | [] -> true | i::xs' -> i mod 2 = 0 && saw_even2 saw_odd2 xs' let start2 xs = match xs with | [] -> true | i::xs' -> if i mod 2 = 0 then saw_even2 saw_odd2 xs' else saw_odd2 xs' (* module system *) (* NO CODE CAN DEPEND ON ANYTHING NOT IN THE MODULE TYPE! *) module type NONEGINT = sig type t val mknni : int -> t option val add : t -> t -> t (* why not t -> t -> int or t -> t -> t option? *) val mul : t -> t -> t val sub : t -> t -> t option val to_int : t -> int (* why not t -> t ? *) end (* this example, thanks to the abstract type in NONEGINT, makes it impossible for a client to make a value of type NonNegInt.t that is negative. However, this is assuming (wrongly) that ints do not overflow. That assumption is useful for a simple example that conveys the idea of relying on an abstraction, but in practice due to overflow, we would need add and mul to check for negatives and return an option, like sub, which makes the example less compelling. *) module NonNegInt : NONEGINT = struct type t = int let mknni i = if i < 0 then None else Some i let add a b = a + b (* why not mknni (a * b) ? See comment above. *) let mul a b = a * b (* why not mknni (a * b) ? See comment above. *) let sub a b = mknni (a - b) (* why not a - b ? *) let to_int a = a (* external world doesn't know this "is it" *) end (* This example shows three different ways to implement the same abstraction where, thanks to the abstract type, all three behave exactly the same for all clients. Each uses a different data structure and therefore has different algorithms and invariants. *) module type NONEMPTYLIST = sig type 'a t val single : 'a -> 'a t val cons : 'a -> 'a t -> 'a t val tl : 'a t -> 'a t val hd : 'a t -> 'a val len : 'a t -> int val map : ('a -> 'b) -> 'a t -> 'b t end module Nel_A : NONEMPTYLIST = struct type 'a t = | Single of 'a | Cons of 'a * 'a t let single x = Single x let cons x xs = Cons (x,xs) let tl xs = match xs with | Single _ -> failwith "tl of one-element list" | Cons(_,xs') -> xs' let hd xs = match xs with | Single x -> x | Cons(x,_) -> x let rec len xs = match xs with | Single _ -> 1 | Cons(_,xs') -> 1 + len xs' let rec map f xs = match xs with | Single x -> Single (f x) | Cons (x,xs') -> Cons(f x, map f xs') end module Nel_B : NONEMPTYLIST = struct type 'a t = 'a * ('a list) let single x = (x,[]) let cons x (y,xs) = (x,y::xs) let tl xs = match xs with | (_,[]) -> failwith "tl of one-element list" | (_,y::ys) -> (y,ys) let hd = fst let len (_,xs') = 1 + List.length xs' let map f (x,xs') = (f x, List.map f xs') end module Nel_C : NONEMPTYLIST = struct type 'a t = 'a list let single x = [x] let cons x xs = x::xs let tl xs = match xs with | [] -> failwith "impossible -- can never happen" | x::[] -> failwith "tl of one-element list" | x::xs' -> xs' let hd = List.hd let len = List.length let map = List.map end (* CSE 341, Section 4, Completed/Solution Code *) (* mutual recursion, just since we haven't shown it to you *) (* can do any finite state machine this way -- this one checks for strictly alternating even/odd mutual recursion is often not tail-recursive, but for finite state machines it is *) let rec start xs = match xs with | [] -> true | i::xs' -> if i mod 2 = 0 then saw_even xs' else saw_odd xs' and saw_even xs = match xs with | [] -> true | i::xs' -> i mod 2 <> 0 && saw_odd xs' and saw_odd xs = match xs with | [] -> true | i::xs' -> i mod 2 = 0 && saw_even xs' (* style above is correct, but we could if we really had to use first-class functions to encode mutual recursion by having earlier functions take function arguments and then calling them with later functions *) let saw_even2 f xs = match xs with | [] -> true | i::xs' -> i mod 2 <> 0 && f xs' let rec saw_odd2 xs = match xs with | [] -> true | i::xs' -> i mod 2 = 0 && saw_even2 saw_odd2 xs' let start2 xs = match xs with | [] -> true | i::xs' -> if i mod 2 = 0 then saw_even2 saw_odd2 xs' else saw_odd2 xs' (* module system *) (* NO CODE CAN DEPEND ON ANYTHING NOT IN THE MODULE TYPE! *) module type NONEGINT = sig type t val mknni : int -> t option val add : t -> t -> t (* why not t -> t -> int or t -> t -> t option? *) val mul : t -> t -> t val sub : t -> t -> t option val to_int : t -> int (* why not t -> t ? *) end (* this example, thanks to the abstract type in NONEGINT, makes it impossible for a client to make a value of type NonNegInt.t that is negative. However, this is assuming (wrongly) that ints do not overflow. That assumption is useful for a simple example that conveys the idea of relying on an abstraction, but in practice due to overflow, we would need add and mul to check for negatives and return an option, like sub, which makes the example less compelling. *) module NonNegInt : NONEGINT = struct type t = int let mknni i = if i < 0 then None else Some i let add a b = a + b (* why not mknni (a * b) ? See comment above. *) let mul a b = a * b (* why not mknni (a * b) ? See comment above. *) let sub a b = mknni (a - b) (* why not a - b ? *) let to_int a = a (* external world doesn't know this "is it" *) end (* This example shows three different ways to implement the same abstraction where, thanks to the abstract type, all three behave exactly the same for all clients. Each uses a different data structure and therefore has different algorithms and invariants. *) module type NONEMPTYLIST = sig type 'a t val single : 'a -> 'a t val cons : 'a -> 'a t -> 'a t val tl : 'a t -> 'a t val hd : 'a t -> 'a val len : 'a t -> int val map : ('a -> 'b) -> 'a t -> 'b t end module Nel_A : NONEMPTYLIST = struct type 'a t = | Single of 'a | Cons of 'a * 'a t let single x = Single x let cons x xs = Cons (x,xs) let tl xs = match xs with | Single _ -> failwith "tl of one-element list" | Cons(_,xs') -> xs' let hd xs = match xs with | Single x -> x | Cons(x,_) -> x let rec len xs = match xs with | Single _ -> 1 | Cons(_,xs') -> 1 + len xs' let rec map f xs = match xs with | Single x -> Single (f x) | Cons (x,xs') -> Cons(f x, map f xs') end module Nel_B : NONEMPTYLIST = struct type 'a t = 'a * ('a list) let single x = (x,[]) let cons x (y,xs) = (x,y::xs) let tl xs = match xs with | (_,[]) -> failwith "tl of one-element list" | (_,y::ys) -> (y,ys) let hd = fst let len (_,xs') = 1 + List.length xs' let map f (x,xs') = (f x, List.map f xs') end module Nel_C : NONEMPTYLIST = struct type 'a t = 'a list let single x = [x] let cons x xs = x::xs let tl xs = match xs with | [] -> failwith "impossible -- can never happen" | x::[] -> failwith "tl of one-element list" | x::xs' -> xs' let hd = List.hd let len = List.length let map = List.map end
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!