ADTAbstract Data Types
From Coq Require Import Strings.String. (* for manual grading *)
From VFA Require Import Perm.
From VFA Require Import Maps.
From VFA Require Import SearchTree.
Let's consider the concept of lookup tables, indexed by keys that are
natural numbers, mapping those keys to values of arbitrary (parametric) type.
We can express this in Coq as follows:
Module Type TABLE.
Parameter V: Type.
Parameter default: V.
Parameter table: Type.
Definition key := nat.
Parameter empty: table.
Parameter get: key → table → V.
Parameter set: key → V → table → table.
Axiom gempty: ∀ k, (* get-empty *)
get k empty = default.
Axiom gss: ∀ k v t, (* get-set-same *)
get k (set k v t) = v.
Axiom gso: ∀ j k v t, (* get-set-other *)
j ≠ k → get j (set k v t) = get j t.
End TABLE.
This means: in any Module that satisfies this Module Type,
there's a type table of lookup-tables, a type V of values,
and operators empty, get, set that satisfy the axioms
gempty, gss, and gso.
For example, we can easily give an implementation of TABLE in terms
of our function-based maps. For specificity, we'll take V to be
the type bool and use a default value of false.
Module MapsTable <: TABLE.
Definition V := bool.
Definition default: V := false.
Definition key := nat.
Definition table := total_map V.
Definition empty : table := t_empty default.
Definition get (k: key) (m: table) : V := m k.
Definition set (k: key) (v: V) (m: table) : table := t_update m k v.
Theorem gempty: ∀ k,
get k empty = default.
Proof. intros. apply t_apply_empty. Qed.
Theorem gss: ∀ k v t,
get k (set k v t) = v.
Proof. intros. apply t_update_eq. Qed.
Theorem gso: ∀ j k v t,
j≠k →
get j (set k v t) = get j t.
Proof. intros. apply t_update_neq. omega. Qed.
End MapsTable.
The specialization of TABLE to TABLE_STRING exposes the
definition of V as string to the world outside the module.
Without it, all that would be known is what TABLE exposes, which
is that V is some Type.
Now, let's calculate: put (1,true) and then (3,false) into a map, then lookup 1.
Eval compute in MapsTable.get 1 (MapsTable.set 3 false (MapsTable.set 1 true MapsTable.empty)).
(* = true *)
An Abstract Data Type comprises:
So, MapsTable is an implementation of the TABLE abstract type.
Give an alternative implementation of TABLE using lists of (key,value) pairs
as the hidden representation. Again, take V to be bool and default
to be false.
- A type with a hidden representation (in this case, t).
- Interface functions that operate on that type (empty, get, set).
- Axioms about the interaction of those functions (gempty, gss, gso).
Exercise: 4 stars, standard (ListsTable)
(* Do not modify the following line: *)
Definition manual_grade_for_ListsTable : option (nat×string) := None.
☐
Module TreeTable <: TABLE.
Definition V := bool.
Definition default : V := false.
Definition table := tree V.
Definition key := nat.
Definition empty : table := empty_tree V.
Definition get (k: key) (m: table) : V := lookup V default k m.
Definition set (k: key) (v: V) (m: table) : table :=
insert V k v m.
Definition elements (m:table) : list (key×V) :=
elements V m.
Theorem gempty: ∀ k,
get k empty = default.
Proof. intros. apply lookup_empty. Qed.
Theorem gss: ∀ k v t,
get k (set k v t) = v.
Proof. intros. apply lookup_insert_eq. Qed.
Theorem gso: ∀ j k v t,
j≠k →
get j (set k v t) = get j t.
Proof. intros. apply lookup_insert_neq. omega. Qed.
End TreeTable.
Now let's consider a richer interface ETABLE for Tables that support
an elements operation.
Module Type ETABLE.
Parameter V: Type.
Parameter default: V.
Parameter table: Type.
Definition key := nat.
Parameter empty: table.
Parameter get: key → table → V.
Parameter set: key → V → table → table.
Parameter elements: table → list (key×V).
Axiom gempty: ∀ k, (* get-empty *)
get k empty = default.
Axiom gss: ∀ k v t, (* get-set-same *)
get k (set k v t) = v.
Axiom gso: ∀ j k v t, (* get-set-other *)
j ≠ k →
get j (set k v t) = get j t.
Axiom gie: ∀ k v t,
get k t = v →
In (k,v) (elements t) ∨ ¬ In k (map fst (elements t)) ∧ v = default.
Axiom ieg: ∀ k v t,
In (k,v) (elements t) →
get k t = v.
Axiom edistinct : ∀ t,
NoDup (map fst (elements t)).
End ETABLE.
Since we know our search tree implementation supports elements, let's try to use it:
Module TreeETable <: ETABLE.
Definition V := bool.
Definition default : V := false.
Definition table := tree V.
Definition key := nat.
Definition empty : table := empty_tree V.
Definition get (k: key) (m: table) : V := lookup V default k m.
Definition set (k: key) (v: V) (m: table) : table :=
insert V k v m.
Definition elements (m:table) : list (key×V) :=
elements V m.
Theorem gempty: ∀ k,
get k empty = default.
Proof. intros. apply lookup_empty. Qed.
Theorem gss: ∀ k v t,
get k (set k v t) = v.
Proof. intros. apply lookup_insert_eq. Qed.
Theorem gso: ∀ j k v t,
j≠k →
get j (set k v t) = get j t.
Proof. intros. apply lookup_insert_neq. omega. Qed.
Theorem gie: ∀ k v t,
get k t = v →
In (k,v) (elements t) ∨ ¬ In k (map fst (elements t)) ∧ v = default.
Proof.
intros. apply lookup_In_elements; auto.
(* oops! we don't know this *)
Admitted.
Theorem ieg: ∀ k v t,
In (k,v) (elements t) →
get k t = v.
Proof.
intros. apply In_elements_lookup; auto.
Admitted.
Theorem edistinct : ∀ t,
NoDup (map fst (elements t)).
Proof.
intros. apply elements_keys_distinct; auto.
Admitted.
End TreeETable.
Giving proofs of the specifications involving elements requires that
t have the BST property. This is a common problem with ADTs: we need
values of the (hidden) representation type to be well-formed, i.e. to satisfy
some invariant. ALthough we know that the official operations on ADTs do
maintain the invariant, we must ensure that the client of an ADT
cannot "forge" values, that is, cannot coerce the representation type into
the abstract type; especially ill-formed values of the representation type.
This "unforgeability" is enforced in some real programming languages:
ML (Standard ML or Ocaml) with its module system; Java, whose Classes
can have "private variables" that the client cannot see.
Let's briefly look at a different way of enforcing the
representation invariant: every value of the representation type
will carry along with it a proof that the value satisfies the
representation invariant.
To do this, we'll use sigma types, aka dependent sums, which
are defined in the Coq standard library. Suppose P is a
predicate on type A, that is, P : A → Prop. Then sig A P is the
type of elements of A that satisfy P. You could think of this
as a subset: sig A P is the subset of values of A that satisfy
P. Based on that intuition, there is a notation {x : A | P}
that means sig A P.
A Brief Excursion into Dependent Types
Check sig.
(* ====>
sig : forall A : Type, (A -> Prop) -> Type *)
(* As an example, here is a type for even natural numbers. Nat.Even : nat → Prop is defined in the standard library. The A argument to
sig is implicit, so we don't write it below. *)
Definition even_nat := sig Nat.Even.
Print even_nat.
(* ====>
even_nat = {x : nat | Nat.Even x}
: Set
*)
Check exist.
(* forall {A : Type} (P : A -> Prop) (x : A),
P x -> {x | P x} *)
Check proj1_sig.
(* forall {A : Type} {P : A -> Prop},
{x | P x} -> A *)
Check proj2_sig.
(* forall (A : Type) {P : A -> Prop} (e : {x | P x}),
P (proj1_sig e) *)
We'll apply that idea to search trees. The type A will be tree V.
The predicate P(x) will be BST(x).
Module TreeETable2 <: ETABLE.
Definition V := bool.
Definition default : V := false.
Definition table := {x | @BST V x}.
Definition key := nat.
Definition empty : table :=
exist (BST V) (empty_tree V) (empty_tree_BST V).
Definition get (k: key) (m: table) : V :=
(lookup V default k (proj1_sig m)).
Definition set (k: key) (v: V) (m: table) : table :=
exist (BST V) (insert V k v (proj1_sig m))
(insert_BST _ _ _ _ (proj2_sig m)).
Definition elements (m:table) := elements V (proj1_sig m).
Theorem gempty: ∀ k,
get k empty = default.
Proof. intros. reflexivity. Qed.
Theorem gss: ∀ k v t,
get k (set k v t) = v.
Proof. intros. apply lookup_insert_eq. Qed.
Theorem gso: ∀ j k v t,
j≠k →
get j (set k v t) = get j t.
Proof. intros. apply lookup_insert_neq. omega. Qed.
Theorem gie: ∀ k v t,
get k t = v →
In (k,v) (elements t) ∨ ¬ In k (map fst (elements t)) ∧ v = default .
Proof.
intros. apply lookup_In_elements; auto.
Now: t is a package with two components:
The first component is a tree, and the second component is
a proof that the first component has the SearchTree property.
We can destruct t to see that more clearly.
destruct t as [a Ha].
(* Watch what this simpl does: *)
simpl.
auto.
Qed.
Theorem ieg: ∀ k v t,
In (k,v) (elements t) →
get k t = v.
Proof.
intros. apply In_elements_lookup; auto.
destruct t as [a Ha]. simpl. auto.
Qed.
Theorem edistinct : ∀ t,
NoDup (map fst (elements t)).
Proof.
intros.
apply elements_keys_distinct; auto.
destruct t as [a Ha]. simpl. auto.
Qed.
End TreeETable2.
Exercise: 4 stars, advanced, optional (ListsETable)
(* Do not modify the following line: *)
Definition manual_grade_for_ListsETable : option (nat×string) := None.
☐
Abstracting over invariants.
Module Type ETABLE_INV.
Parameter V: Type.
Parameter default: V.
Parameter table: Type.
Definition key := nat.
Parameter empty: table.
Parameter get: key → table → V.
Parameter set: key → V → table → table.
Parameter elements: table → list (key×V).
Parameter is_table: table → Prop.
Axiom is_table_empty: is_table empty.
Axiom is_table_set: ∀ t k v,
is_table t →
is_table (set k v t).
Axiom gempty: ∀ k, (* get-empty *)
get k empty = default.
Axiom gss: ∀ k v t, (* get-set-same *)
get k (set k v t) = v.
Axiom gso: ∀ j k v t, (* get-set-other *)
j ≠ k →
get j (set k v t) = get j t.
Axiom gie: ∀ k v t,
is_table t →
get k t = v →
In (k,v) (elements t) ∨ ¬ In k (map fst (elements t)) ∧ v = default.
Axiom ieg: ∀ k v t,
is_table t →
In (k,v) (elements t) →
get k t = v.
Axiom edistinct : ∀ t,
is_table t →
NoDup (map fst (elements t)).
End ETABLE_INV.
Now we can prove that our search trees match this extended signature, by
instantiating the abstract invariant with the BST property, and invoking
the lemmas we proved in SearchTree to show that this property is
correctly maintained. The proofs of the elements-related specifications
now take a BST assumption, and go through easily.
Module TreeETable3 <: ETABLE_INV.
Definition V := bool.
Definition default : V := false.
Definition table := tree V.
Definition key := nat.
Definition empty : table := empty_tree V.
Definition get (k: key) (m: table) : V := lookup V default k m.
Definition set (k: key) (v: V) (m: table) : table :=
insert V k v m.
Definition elements (m:table) : list (key×V) :=
elements V m.
Definition is_table t := BST V t.
Theorem is_table_empty: is_table empty.
Proof. intros. apply empty_tree_BST. Qed.
Theorem is_table_set: ∀ t k v,
is_table t →
is_table (set k v t).
Proof. intros. apply insert_BST; auto. Qed.
Theorem gempty: ∀ k,
get k empty = default.
Proof. intros. apply lookup_empty. Qed.
Theorem gss: ∀ k v t,
get k (set k v t) = v.
Proof. intros. apply lookup_insert_eq. Qed.
Theorem gso: ∀ j k v t,
j≠k →
get j (set k v t) = get j t.
Proof. intros. apply lookup_insert_neq. omega. Qed.
Theorem gie: ∀ k v t,
is_table t →
get k t = v →
In (k,v) (elements t) ∨ ¬ In k (map fst (elements t)) ∧ v = default.
Proof. intros. apply lookup_In_elements; auto. Qed.
Theorem ieg: ∀ k v t,
is_table t →
In (k,v) (elements t) →
get k t = v.
Proof. intros. apply In_elements_lookup; auto. Qed.
Theorem edistinct : ∀ t,
is_table t →
NoDup (map fst (elements t)).
Proof. intros. apply elements_keys_distinct; auto. Qed.
End TreeETable3.
Module signatures for model-based Specification
Module Type ETABLE_INV_MODEL.
Parameter V: Type.
Parameter default: V.
Parameter table: Type.
Definition key := nat.
Parameter empty: table.
Parameter get: key → table → V.
Parameter set: key → V → table → table.
Parameter elements: table → list (key×V).
Parameter is_table: table → Prop.
Axiom is_table_empty: is_table empty.
Axiom is_table_set: ∀ t k v,
is_table t →
is_table (set k v t).
Parameter Abs : table → partial_map V.
Axiom empty_relate: Abs empty = Maps.empty.
Axiom lookup_relate : ∀ t k,
is_table t →
get k t = find V default (Abs t) k.
Axiom insert_relate : ∀ t k v,
is_table t →
Abs (set k v t) = update (Abs t) k v.
Axiom elements_relate : ∀ t,
is_table t →
list2map V (elements t) = Abs t.
End ETABLE_INV_MODEL.
Exercise: 3 stars, standard (TreeTableModel)
(* Do not modify the following line: *)
Definition manual_grade_for_TreeTableModel : option (nat×string) := None.
☐
Exercise: 2 stars, advanced, optional (TreeTableModel2)
(* Do not modify the following line: *)
Definition manual_grade_for_TreeTableModel2 : option (nat×string) := None.
☐
Step 1. Define a representation invariant.
(In the case of search trees, the representation invariant
is the BST predicate.) Prove that each operation on the
data type preserves the representation invariant.
For example:
Check (empty_tree_BST V).
(* BST (empty_tree V) *)
Check (insert_BST V).
(* forall (k : key) (v : V) (t : tree V),
BST t -> BST (insert k v t) *)
Notice two things: Any operator (such as insert) that takes
a tree parameter can assume that the parameter satisfies
the representation invariant. That is, the insert_BST theorem
takes a premise, BST t.
Any operator that produces a tree result must prove that the result
satisfies the representation invariant. Thus, the conclusions,
BST (empty_tree V) and BST (insert k v t)
of the two theorems above.
Finally, any operator that produces a result of "base type", has no obligation
to prove that the result satisfies the representation invariant; that wouldn't
make any sense anyway, because the types wouldn't match. That is,
there's no "lookup_BST" theorem, because lookup doesn't
return a result that's a tree.
Step 2 (only if using model-based specification). Define an abstraction relation.
(In the case of search trees, it's the Abs function.)
This relates the data structure to some mathematical
value that is (presumably) simpler to reason about.
For each operator, prove that: assuming each tree argument
satisfies the representation invariant and the abstraction
relation, prove that the results also satisfy the appropriate
abstraction relation.
Check (empty_relate V). (*
Abs V (empty_tree V) = empty *)
Check (lookup_relate V). (*
forall (d: V) (t : tree V) (k : key),
BST V t ->
lookup V default k t = find V default (Abs V t) k *)
Check (insert_relate V). (*
forall (t : tree V) (k : key) (v : V),
BST V t ->
Abs V (insert V k v t) = update (Abs V t) k v *)
Step 3. Using the representation invariant,
pove that all the axioms of your ADT are valid. For example...
Check TreeETable2.gso. (*
: forall (j k : TreeTable2.key) (v : TreeTable2.V)
(t : TreeTable2.table),
j <> k ->
TreeTable2.get j (TreeTable2.set k v t) = TreeTable2.get j t *)
End ADT_SUMMARY.
Here's the Fibonacci function.
Fixpoint fibonacci (n: nat) :=
match n with
| 0 ⇒ 1
| S i ⇒ match i with 0 ⇒ 1 | S j ⇒ fibonacci i + fibonacci j end
end.
Eval compute in map fibonacci [0;1;2;3;4;5;6].
Here's a silly little program that computes the Fibonacci function.
Fixpoint repeat {A} (f: A→A) (x: A) n :=
match n with O ⇒ x | S n' ⇒ f (repeat f x n') end.
Definition step (al: list nat) : list nat :=
List.cons (nth 0 al 0 + nth 1 al 0) al.
Eval compute in map (repeat step [1;0;0]) [0;1;2;3;4;5].
Definition fib n := nth 0 (repeat step [1;0;0] n) 0.
Eval compute in map fib [0;1;2;3;4;5;6].
Here's a strange "List" module.
Module Type LISTISH.
Parameter list: Type.
Parameter create : nat → nat → nat → list.
Parameter cons: nat → list → list.
Parameter nth: nat → list → nat.
End LISTISH.
Module L <: LISTISH.
Definition list := (nat×nat×nat)%type.
Definition create (a b c: nat) : list := (a,b,c).
Definition cons (i: nat) (il : list) := match il with (a,b,c) ⇒ (i,a,b) end.
Definition nth (n: nat) (al: list) :=
match al with (a,b,c) ⇒
match n with 0 ⇒ a | 1 ⇒ b | 2 ⇒ c | _ ⇒ 0 end
end.
End L.
Definition sixlist := L.cons 0 (L.cons 1 (L.cons 2 (L.create 3 4 5))).
Eval compute in map (fun i ⇒ L.nth i sixlist) [0;1;2;3;4;5;6;7].
Module L implements approximations of lists: it can remember
the first three elements, and forget the rest. Now watch:
Definition stepish (al: L.list) : L.list :=
L.cons (L.nth 0 al + L.nth 1 al) al.
Eval compute in map (repeat stepish (L.create 1 0 0)) [0;1;2;3;4;5].
Definition fibish n := L.nth 0 (repeat stepish (L.create 1 0 0) n).
Eval compute in map fibish [0;1;2;3;4;5;6].
This little theorem may be useful in the next exercise.
Lemma nth_firstn:
∀ A d i j (al: list A), i<j → nth i (firstn j al) d = nth i al d.
Proof.
induction i; destruct j,al; simpl; intros; auto; try omega.
apply IHi. omega.
Qed.
induction i; destruct j,al; simpl; intros; auto; try omega.
apply IHi. omega.
Qed.
Exercise: 4 stars, standard, optional (listish_abstraction)
In this exercise we will not need a representation invariant. Define an abstraction relation:Inductive L_Abs: L.list → List.list nat → Prop :=
(* FILL IN HERE *)
.
Definition O_Abs al al' := L_Abs al al'.
(* State these theorems using O_Abs, not L_Abs.
You'll see why below, at "Opaque". *)
Lemma create_relate : True. (* change this line appropriately *)
(* FILL IN HERE *) Admitted.
Lemma cons_relate : True. (* change this line appropriately *)
(* FILL IN HERE *) Admitted.
Lemma nth_relate : True. (* change this line appropriately *)
(* FILL IN HERE *) Admitted.
Now, we will make these operators opaque. Therefore, in the rest of
the proofs in this exercise, you will not unfold their definitions. Instead,
you will just use the theorems create_relate, cons_relate, nth_relate.
Opaque L.list.
Opaque L.create.
Opaque L.cons.
Opaque L.nth.
Opaque O_Abs.
Lemma step_relate:
∀ al al',
O_Abs al al' →
O_Abs (stepish al) (step al').
Proof.
(* FILL IN HERE *) Admitted.
Lemma repeat_step_relate:
∀ n al al',
O_Abs al al' →
O_Abs (repeat stepish al n) (repeat step al' n).
Proof.
(* FILL IN HERE *) Admitted.
Lemma fibish_correct: ∀ n, fibish n = fib n.
Proof. (* No induction needed in this proof! *)
(* FILL IN HERE *) Admitted.
☐
Exercise: 2 stars, standard, optional (fib_time_complexity)
Suppose you run these three programs call-by-value, that is, as if they were ML programs. fibonacci N fib N fibish N What is the asymptotic time complexity (big-Oh run time) of each, as a function of N? Assume that the plus function runs in constant time. You can use terms like "linear," "N log N," "quadratic," "cubic," "exponential." Explain your answers briefly.fib: (* fill in here *)
fibish: (* fill in here *)
(* Tue Jan 28 13:58:40 EST 2020 *)