Lecture 5.3: Specifications with Modules
Last class we implemented Sets as int lists. Let's take another look at that, but this time we will use a polymorphic set:
module type MySetSig = sig
(* Type of sets *)
type 'a t
(* Construct an empty set *)
val empty : 'a t
(* Set membership *)
val mem : 'a -> 'a t -> bool
(* Insert an element into a set *)
val insert : 'a -> 'a t -> 'a t
(* Remove an element from a set *)
val remove : 'a -> 'a t -> 'a t
end
Comparing this to our previous signature, notice how 'a t replaces t everywhere:
module type MyIntSetSig = sig
type t
val empty : t
val mem : int -> t -> bool
val add : int -> t -> t
val remove : int -> t -> t
end
We can also now define our polymorphic implementation in the same way as before, but this time we will use some standard library functions:
module ListSet : MySetSig = struct
type 'a t = 'a list;;
let mem (x : 'a) (s : 'a t) : bool = List.mem x s
let add (el : 'a) (s : 'a t) : 'a t = el :: s
let remove (x : 'a) (s : 'a t) : 'a t = List.filter ((<>) x) s
end
Specs for sets
What are some algebraic properties we can state about sets?
- nothing is in an empty set: $$ \forall x.\ \neg \verb|mem|\ x\ \verb|empty| $$
- inserting an element means the element is in the set $$ \forall x\ S.\ \verb|mem|\ x\ (\verb|add|\ x\ S) $$
- removing an element from a set means the element is not in the set $$ \forall x\ S.\ \neg \verb|mem|\ x\ (\verb|remove|\ x\ S) $$
- adding an element shouldn't add any additional ones $$ \forall x\ y\ S.\ x \neq y \wedge \neg \verb|mem|\ y\ S \implies \neg \verb|mem|\ y\ (\verb|add|\ x\ S) $$
- removing an element shouldn't remove any other ones $$ \forall x\ y\ S.\ x \neq y \wedge \verb|mem|\ y\ S \implies \verb|mem|\ y\ (\verb|remove|\ x\ S) $$
let's make another set implementation
module FunSet : MySetSig = struct
type 'a t = 'a -> bool;;
let empty = fun (_ : 'a) -> false;;
let mem x s = s x;;
let add x s = fun x' -> if x = x' then true else s x'
let remove x s = fun x' -> if x = x' then false else s x'
end;;
module TestSet = FunSet;;
let prop_mem_empty =
fun (x : string) -> (* forall x ... *)
not (TestSet.mem x TestSet.empty)
;;
let prop_mem_add =
fun (x, s : string * string TestSet.t) -> (* forall x TestSet... *)
TestSet.(mem x (add x s))
;;
let prop_mem_remove =
fun (x, s : string * string TestSet.t) -> (* forall x TestSet... *)
let open TestSet in
not (mem x (remove x s))
;;
let prop_mem_add_other =
fun (x, y, s : string * string * string TestSet.t) -> (* forall x TestSet... *)
let open TestSet in
if x <> y && not (mem y s) then
not (mem y (add x s))
else true
;;
let prop_mem_remove_other =
fun (x, y, s : string * string * string TestSet.t) -> (* forall x TestSet... *)
let open TestSet in
if x <> y && mem y s then
mem y (remove x s)
else true
;;
let rec build_set_acc (n : int) (gen : unit -> 'a) (acc : 'a TestSet.t) : 'a TestSet.t
= if n <= 0
then acc
else (build_set_acc (n - 1) gen (MySet.add (Random.int 10) acc))
let build_set (n : int) (g : unit -> 'a) : 'a TestSet.t = build_set_acc n g TestSet.empty
let gen_int (bound : int) () : int = Random.int bound
let gen2 (g1 : unit -> 'a) (g2 : unit -> 'b) () : 'a * 'b =
(g1 (), g2 ())
let gen3 (g1 : unit -> 'a) (g2 : unit -> 'b) (g3 : unit -> 'c) () : 'a * 'b * 'c =
(g1 (), g2 (), g3 ())
let rec forall
?(trials = 1000)
(gen : unit -> 'a)
(prop : 'a -> bool)
: int option =
if trials <= 0
then None
else
let xs = gen () in
if not (prop xs)
then Some xs
else forall gen prop (trials - 1)
let test_mem_empty () =
forall (fun () -> failwith "...etc" ) prop_mem_empty 10
Booleans as an abstract type
Last class we gained more practice with abstract types: how to write them using modules and module types, and how we can model various data structures and existing concepts using abstract types.
Today, we will see how abstract types are useful for program correctness. Recall our slogan: "abstract types prevent users from doing bad things". Is there a way to make this more precise? What is a "bad thing"?
What about this?
module type BSig = sig
type t
val tru : t
val fls : t
val ite : t -> 'a -> 'a -> 'a
end
(* module B : BSig = struct ... end *)
What can we do here? We can have tru : t and fls : t, and we can case on a t by using ite. While we can case on a t, there are still only two values of t we can use. This means that M2.t is in bijection with the booleans: any boolean can be turned into a t, and vice versa.
We can actually implement these functions:
(* module M : S = struct ... end *)
let t_of_bool (b : bool) : B.t = if b then B.tru else B.fls
let bool_of_t (b : B.t) : bool = B.ite b true false
Let's now implement it: