Week 2: Data Abstraction
- Author
Adam Chlipala, with modifications by CS-428 staff.
- License
No redistribution allowed (usage by permission in CS-428).
Perhaps the essence of effective programming is division of large tasks into smaller ones, and data abstraction is a key technique to that end. We provide a clear separation between interfaces and implementations. The author of a library can take responsibility for making it implement an interface faithfully, encapsulating private state and other implementation details in a way that client code can't observe. Then that client code can mix and match implementations of some well-specified functionality.
As part of our quick tour through effective Coq programming, we will dwell on patterns for data abstraction, including how to state it formally, from the perspectives of both libraries and client code.
Algebraic specifications
Module Algebraic.
Specying a queue in axiomatic style
One of the classic formalisms for data abstraction is the algebraic style, where requirements on implementations are written out as quantified equalities. Any implementation must satisfy these equalities, but we grant implementations freedom in internal details.
Here's an example of an algebraic interface or specification ("spec" for short). It's for purely functional queues, which follow first-in-first-out disciplines.
Module Type QUEUE. Parameter t : Set -> Set.
An implementation must include some data type t
.
Actually, it's more of a type family, e.g. like list
and some other
polymorphic container types we looked at last time.
Parameter empty : forall A, t A.
For any type A
of data, we can build a queue for that element type.
Parameter enqueue : forall {A}, t A -> A -> t A.
Enqueue a new element, in the functional style, where we build a new queue instead of modifying the original.
Parameter dequeue : forall {A}, t A -> option (t A * A).
Given a queue, either return None
if it is empty or Some (q', v)
for
the result of dequeuing one element, where q'
is the new queue (now
one element shorter) and v
is the element we dequeue.
Which algebraic properties characterize correct queues?
First, dequeue
returns None
exactly on empty queues.
Axiom dequeue_empty : forall A, dequeue (empty A) = None. Axiom empty_dequeue : forall A (q : t A), dequeue q = None -> q = empty A.
Second, dequeue
forms a kind of inverse for enqueue
.
Axiom dequeue_enqueue : forall A (q : t A) x, dequeue (enqueue q x) = Some (match dequeue q with | None => (empty A, x) | Some (q', y) => (enqueue q' x, y) end).
These properties turn out to be enough to prove interesting properties about client code that uses queues. Before we get there, we should define some concrete queue implementations. (If we don't give an implementation, we often realize that the spec is unrealizable!)
End QUEUE.
Verifying a simple queue
First, there is a fairly straightforward implementation with lists.
Module ListQueue : QUEUE. Definition t : Set -> Set := list.
Note that we use identifier list
alone as a first-class type family,
without specifying a parameter explicitly.
We follow the convention of enqueuing onto the fronts of lists and
dequeuing from the backs, so the first two operations are just the first
two constructors of list
.
Definition empty A : t A := nil. Definition enqueue {A} (q : t A) (x : A) : t A := x :: q.
dequeue
is a little more work: we use recursion to step down to the
last element of a list.
Fixpoint dequeue {A} (q : t A) : option (t A * A) := match q with | [] => None | x :: q' => match dequeue q' with | None => Some ([], x) | Some (q'', y) => Some (x :: q'', y) end end.
Applying our experience so far with Coq proofs, the algebraic laws are unremarkable to establish.
forall A : Set, dequeue (empty A) = Noneforall A : Set, dequeue (empty A) = Nonecongruence. Qed.forall A : Set, None = Noneforall (A : Set) (q : t A), dequeue q = None -> q = empty Aforall (A : Set) (q : t A), dequeue q = None -> q = empty Aforall (A : Set) (q : t A), dequeue q = None -> q = []A: Setdequeue [] = None -> [] = []A: Set
a: A
q: list Adequeue (a :: q) = None -> a :: q = []reflexivity.A: Setdequeue [] = None -> [] = []A: Set
a: A
q: list Adequeue (a :: q) = None -> a :: q = []destruct (dequeue q) as [ [? ?] | ]; discriminate. Qed.A: Set
a: A
q: list Amatch dequeue q with | Some (q'', y) => Some (a :: q'', y) | None => Some ([], a) end = None -> a :: q = []forall (A : Set) (q : t A) (x : A), dequeue (enqueue q x) = Some match dequeue q with | Some (q', y) => (enqueue q' x, y) | None => (empty A, x) endforall (A : Set) (q : t A) (x : A), dequeue (enqueue q x) = Some match dequeue q with | Some (q', y) => (enqueue q' x, y) | None => (empty A, x) enddestruct (dequeue q) as [ [? ?] | ]; reflexivity. Qed. End ListQueue.A: Set
q: t A
x: Amatch dequeue q with | Some (q'', y) => Some (x :: q'', y) | None => Some ([], x) end = Some match dequeue q with | Some (q', y) => (enqueue q' x, y) | None => (empty A, x) end
There are software-engineering benefits to interface-implementation separation even when one only bothers to build a single implementation. However, often there are naive and clever optimized versions of a single interface. Queues are no exception. Before we get to a truly clever version, we'll demonstrate with a less obviously better version: enqueuing at the back and dequeuing from the front.
Module ReversedListQueue : QUEUE. Definition t : Set -> Set := list.
Still the same internal queue type, but note that Coq's type system
prevents client code from knowing that fact! t
appears opaque
or abstract from the outside, as we'll see shortly.
The first two operations are similar, but now we enqueue at the list end.
Definition empty A : t A := []. Definition enqueue {A} (q : t A) (x : A) : t A := q ++ [x].
dequeue
is now constant-time, with no recursion and just a single
pattern match.
Definition dequeue {A} (q : t A) : option (t A * A) := match q with | [] => None | x :: q' => Some (q', x) end.
The proofs of the laws are still boring.
forall A : Set, dequeue (empty A) = Nonereflexivity. Qed.forall A : Set, dequeue (empty A) = Noneforall (A : Set) (q : t A), dequeue q = None -> q = empty Aforall (A : Set) (q : t A), dequeue q = None -> q = empty AA: Setdequeue [] = None -> [] = []A: Set
a: A
q: list Adequeue (a :: q) = None -> a :: q = []all: congruence. Qed.A: SetNone = None -> [] = []A: Set
a: A
q: list ASome (q, a) = None -> a :: q = []forall (A : Set) (q : t A) (x : A), dequeue (enqueue q x) = Some match dequeue q with | Some (q', y) => (enqueue q' x, y) | None => (empty A, x) endforall (A : Set) (q : t A) (x : A), dequeue (enqueue q x) = Some match dequeue q with | Some (q', y) => (enqueue q' x, y) | None => (empty A, x) endA: Setforall x : A, match [] ++ [x] with | [] => None | x0 :: q' => Some (q', x0) end = Some (empty A, x)A: Set
a: A
q: list Aforall x : A, match (a :: q) ++ [x] with | [] => None | x0 :: q' => Some (q', x0) end = Some (q ++ [x], a)all: reflexivity. Qed. End ReversedListQueue.A: Setforall x : A, Some ([], x) = Some (empty A, x)A: Set
a: A
q: list Aforall x : A, Some (q ++ [x], a) = Some (q ++ [x], a)
Let's take a look at some client code that is agnostic to queue implementation details. We have been using Coq's module system, inspired by those of the ML programming languages, to encode interfaces and implementations. Coq also adopts from ML the idea of functors, or functions from modules to modules.
Proving client code against the Queue interface
Module DelayedSum (Q : QUEUE).
The code in this scope may refer to some unknown implementation Q
of
the QUEUE
interface.
We will only use a simple example here: enqueue the first n
natural
numbers and then successively dequeue them, computing the sum as we
go.
First, the function to enqueue the first n
natural numbers.
Fixpoint makeQueue (n : nat) (q : Q.t nat) : Q.t nat := match n with | 0 => q | S n' => makeQueue n' (Q.enqueue q n') end.
Next, the function to dequeue repeatedly, keeping a sum.
Fixpoint computeSum (n : nat) (q : Q.t nat) : nat := match n with | 0 => 0 | S n' => match Q.dequeue q with | None => 0 | Some (q', v) => v + computeSum n' q' end end.
This function gives the expected answer, in a simpler form, of
computeSum
after makeQueue
.
Fixpoint sumUpto (n : nat) : nat := match n with | 0 => 0 | S n' => n' + sumUpto n' end.
A crucial lemma: what results from dequeuing out of a makeQueue
call? The answer depends on whether the initial queue q
has anything
to dequeue.
forall (n : nat) (q : Q.t nat), Q.dequeue (makeQueue n q) = match Q.dequeue q with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' q, n') end endforall (n : nat) (q : Q.t nat), Q.dequeue (makeQueue n q) = match Q.dequeue q with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' q, n') end endq: Q.t natQ.dequeue q = match Q.dequeue q with | Some (q', v) => Some (q', v) | None => None endn: nat
IHn: forall q : Q.t nat, Q.dequeue (makeQueue n q) = match Q.dequeue q with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' q, n') end end
q: Q.t natQ.dequeue (makeQueue n (Q.enqueue q n)) = match Q.dequeue q with | Some (q', v) => Some (makeQueue n (Q.enqueue q' n), v) | None => Some (makeQueue n q, n) enddestruct (Q.dequeue q) as [ [? ?] | ]; reflexivity.q: Q.t natQ.dequeue q = match Q.dequeue q with | Some (q', v) => Some (q', v) | None => None endn: nat
IHn: forall q : Q.t nat, Q.dequeue (makeQueue n q) = match Q.dequeue q with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' q, n') end end
q: Q.t natQ.dequeue (makeQueue n (Q.enqueue q n)) = match Q.dequeue q with | Some (q', v) => Some (makeQueue n (Q.enqueue q' n), v) | None => Some (makeQueue n q, n) endn: nat
IHn: forall q : Q.t nat, Q.dequeue (makeQueue n q) = match Q.dequeue q with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' q, n') end end
q: Q.t natmatch Q.dequeue (Q.enqueue q n) with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' (Q.enqueue q n), n') end end = match Q.dequeue q with | Some (q', v) => Some (makeQueue n (Q.enqueue q' n), v) | None => Some (makeQueue n q, n) end
A crucial step! The first use of a law from the interface:
n: nat
IHn: forall q : Q.t nat, Q.dequeue (makeQueue n q) = match Q.dequeue q with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' q, n') end end
q: Q.t nat(let (q', v) := match Q.dequeue q with | Some (q', y) => (Q.enqueue q' n, y) | None => (Q.empty nat, n) end in Some (makeQueue n q', v)) = match Q.dequeue q with | Some (q', v) => Some (makeQueue n (Q.enqueue q' n), v) | None => Some (makeQueue n q, n) endn: nat
IHn: forall q : Q.t nat, Q.dequeue (makeQueue n q) = match Q.dequeue q with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' q, n') end end
q, t: Q.t nat
n0: nat
Hdeq: Q.dequeue q = Some (t, n0)Some (makeQueue n (Q.enqueue t n), n0) = Some (makeQueue n (Q.enqueue t n), n0)n: nat
IHn: forall q : Q.t nat, Q.dequeue (makeQueue n q) = match Q.dequeue q with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' q, n') end end
q: Q.t nat
Hdeq: Q.dequeue q = NoneSome (makeQueue n (Q.empty nat), n) = Some (makeQueue n q, n)reflexivity.n: nat
IHn: forall q : Q.t nat, Q.dequeue (makeQueue n q) = match Q.dequeue q with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' q, n') end end
q, t: Q.t nat
n0: nat
Hdeq: Q.dequeue q = Some (t, n0)Some (makeQueue n (Q.enqueue t n), n0) = Some (makeQueue n (Q.enqueue t n), n0)n: nat
IHn: forall q : Q.t nat, Q.dequeue (makeQueue n q) = match Q.dequeue q with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' q, n') end end
q: Q.t nat
Hdeq: Q.dequeue q = NoneSome (makeQueue n (Q.empty nat), n) = Some (makeQueue n q, n)
Another law!
reflexivity. Qed.n: nat
IHn: forall q : Q.t nat, Q.dequeue (makeQueue n q) = match Q.dequeue q with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' q, n') end end
q: Q.t nat
Hdeq: Q.dequeue q = NoneSome (makeQueue n (Q.empty nat), n) = Some (makeQueue n (Q.empty nat), n)
Now we can tackle the final property directly by induction.
forall n : nat, computeSum n (makeQueue n (Q.empty nat)) = sumUpto nforall n : nat, computeSum n (makeQueue n (Q.empty nat)) = sumUpto n0 = 0n: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto nmatch Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) with | Some (q', v) => v + computeSum n q' | None => 0 end = n + sumUpto nreflexivity.0 = 0n: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto nmatch Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) with | Some (q', v) => v + computeSum n q' | None => 0 end = n + sumUpto nn: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto nmatch match Q.dequeue (Q.enqueue (Q.empty nat) n) with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' (Q.enqueue (Q.empty nat) n), n') end end with | Some (q', v) => v + computeSum n q' | None => 0 end = n + sumUpto nn: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto nmatch (let (q', v) := match Q.dequeue (Q.empty nat) with | Some (q', y) => (Q.enqueue q' n, y) | None => (Q.empty nat, n) end in Some (makeQueue n q', v)) with | Some (q', v) => v + computeSum n q' | None => 0 end = n + sumUpto nn: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto nn + computeSum n (makeQueue n (Q.empty nat)) = n + sumUpto ncongruence. Qed. End DelayedSum. End Algebraic.n: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto nn + sumUpto n = n + sumUpto n
Algebraic specifications with equivalences
There is a famous implementation of queues with two stacks, achieving amortized constant time for all operations, in contrast to the worst-case quadratic time of both queue implementations we just saw. However, to justify this fancy implementation, we will need to choose a more permissive interface, based on the idea of parameterizing over an arbitrary equivalence relation between queues, which need not be simple congruence.
Module AlgebraicWithEquivalenceRelation. Module Type QUEUE.
We still have a type family of queues, plus the same three operations.
Parameter t : Set -> Set. Parameter empty : forall A, t A. Parameter enqueue : forall {A}, t A -> A -> t A. Parameter dequeue : forall {A}, t A -> option (t A * A).
What's new? This equivalence relation. The type Prop
stands for
logical truth values, so a function returning it can be seen as a
relation in the usual mathematical sense. This is a binary relation,
in particular, since it takes two arguments.
Parameter equiv : forall {A}, t A -> t A -> Prop.
Let's declare convenient syntax for the relation.
Infix "~=" := equiv (at level 70).
It really is an equivalence relation, as formalized by the usual three laws.
Axiom equiv_refl : forall {A} (a : t A), a ~= a. Axiom equiv_sym : forall {A} (a b : t A), a ~= b -> b ~= a. Axiom equiv_trans : forall {A} (a b c : t A), a ~= b -> b ~= c -> a ~= c.
It must be the case that enqueuing elements preserves the relation.
Axiom equiv_enqueue : forall A (a b : t A) (x : A), a ~= b -> enqueue a x ~= enqueue b x.
We define a derived relation for results of dequeue
: either both
dequeue
s failed to return anything, or both returned the same data
value along with new queues that are themselves related.
Definition dequeue_equiv {A} (a b : option (t A * A)) := match a, b with | None, None => True | Some (qa, xa), Some (qb, xb) => qa ~= qb /\ xa = xb | _, _ => False end. Infix "~~=" := dequeue_equiv (at level 70). Axiom equiv_dequeue : forall {A} (a b : t A), a ~= b -> dequeue a ~~= dequeue b.
We retain the three axioms from the prior interface, using our fancy relation instead of equality on queues.
Axiom dequeue_empty : forall {A}, dequeue (empty A) = None. Axiom empty_dequeue : forall {A} (q : t A), dequeue q = None -> q ~= empty A. Axiom dequeue_enqueue : forall {A} (q : t A) x, dequeue (enqueue q x) ~~= match dequeue q with | None => Some (empty A, x) | Some (q', y) => Some (enqueue q' x, y) end. End QUEUE.
It's easy to redo ListQueue
, specifying normal equality for the
equivalence relation.
Module ListQueue : QUEUE. Definition t : Set -> Set := list. Definition empty A : t A := nil. Definition enqueue {A} (q : t A) (x : A) : t A := x :: q. Fixpoint dequeue {A} (q : t A) : option (t A * A) := match q with | [] => None | x :: q' => match dequeue q' with | None => Some ([], x) | Some (q'', y) => Some (x :: q'', y) end end. Definition equiv {A} (a b : t A) := a = b. Infix "~=" := equiv (at level 70).forall (A : Set) (a : t A), a ~= acongruence. Qed.forall (A : Set) (a : t A), a ~= aforall (A : Set) (a b : t A), a ~= b -> b ~= acongruence. Qed.forall (A : Set) (a b : t A), a ~= b -> b ~= aforall (A : Set) (a b c : t A), a ~= b -> b ~= c -> a ~= ccongruence. Qed.forall (A : Set) (a b c : t A), a ~= b -> b ~= c -> a ~= cforall (A : Set) (a b : t A) (x : A), a ~= b -> enqueue a x ~= enqueue b xunfold equiv; congruence. Qed. Definition dequeue_equiv {A} (a b : option (t A * A)) := match a, b with | None, None => True | Some (qa, xa), Some (qb, xb) => qa ~= qb /\ xa = xb | _, _ => False end. Infix "~~=" := dequeue_equiv (at level 70).forall (A : Set) (a b : t A) (x : A), a ~= b -> enqueue a x ~= enqueue b xforall (A : Set) (a b : t A), a ~= b -> dequeue a ~~= dequeue bforall (A : Set) (a b : t A), a ~= b -> dequeue a ~~= dequeue bforall (A : Set) (a b : t A), a = b -> match dequeue a with | Some (qa, xa) => match dequeue b with | Some (qb, xb) => qa ~= qb /\ xa = xb | None => False end | None => match dequeue b with | Some _ => False | None => True end endA: Set
a, b: t A
Heq: a = bmatch dequeue a with | Some (qa, xa) => match dequeue b with | Some (qb, xb) => qa ~= qb /\ xa = xb | None => False end | None => match dequeue b with | Some _ => False | None => True end endA: Set
a, b: t A
Heq: a = bmatch dequeue b with | Some (qa, xa) => match dequeue b with | Some (qb, xb) => qa ~= qb /\ xa = xb | None => False end | None => match dequeue b with | Some _ => False | None => True end endA: Set
a, b: t A
Heq: a = b
t0: t A
a0: At0 ~= t0 /\ a0 = a0A: Set
a, b: t A
Heq: a = bTruesplit; reflexivity.A: Set
a, b: t A
Heq: a = b
t0: t A
a0: At0 ~= t0 /\ a0 = a0tauto. Qed.A: Set
a, b: t A
Heq: a = bTrueforall A : Set, dequeue (empty A) = Nonereflexivity. Qed.forall A : Set, dequeue (empty A) = Noneforall (A : Set) (q : t A), dequeue q = None -> q ~= empty Aforall (A : Set) (q : t A), dequeue q = None -> q ~= empty AA: SetNone = None -> [] ~= empty AA: Set
a: A
q: list Amatch dequeue q with | Some (q'', y) => Some (a :: q'', y) | None => Some ([], a) end = None -> a :: q ~= empty Areflexivity.A: SetNone = None -> [] ~= empty Adestruct (dequeue q) as [ [? ?] | ]; congruence. Qed.A: Set
a: A
q: list Amatch dequeue q with | Some (q'', y) => Some (a :: q'', y) | None => Some ([], a) end = None -> a :: q ~= empty Aforall (A : Set) (q : t A) (x : A), dequeue (enqueue q x) ~~= match dequeue q with | Some (q', y) => Some (enqueue q' x, y) | None => Some (empty A, x) endforall (A : Set) (q : t A) (x : A), dequeue (enqueue q x) ~~= match dequeue q with | Some (q', y) => Some (enqueue q' x, y) | None => Some (empty A, x) endforall (A : Set) (q : t A) (x : A), match dequeue (enqueue q x) with | Some (qa, xa) => match match dequeue q with | Some (q', y) => Some (enqueue q' x, y) | None => Some (empty A, x) end with | Some (qb, xb) => qa = qb /\ xa = xb | None => False end | None => match match dequeue q with | Some (q', y) => Some (enqueue q' x, y) | None => Some (empty A, x) end with | Some _ => False | None => True end endA: Set
x: A[] = empty A /\ x = xA: Set
a: A
q: list A
IHq: forall x : A, match dequeue (enqueue q x) with | Some (qa, xa) => match match dequeue q with | Some (q', y) => Some (enqueue q' x, y) | None => Some (empty A, x) end with | Some (qb, xb) => qa = qb /\ xa = xb | None => False end | None => match match dequeue q with | Some (q', y) => Some (enqueue q' x, y) | None => Some (empty A, x) end with | Some _ => False | None => True end end
x: Amatch match match dequeue q with | Some (q'', y) => Some (a :: q'', y) | None => Some ([], a) end with | Some (q'', y) => Some (x :: q'', y) | None => Some ([], x) end with | Some (qa, xa) => match match match dequeue q with | Some (q'', y) => Some (a :: q'', y) | None => Some ([], a) end with | Some (q', y) => Some (enqueue q' x, y) | None => Some (empty A, x) end with | Some (qb, xb) => qa = qb /\ xa = xb | None => False end | None => match match match dequeue q with | Some (q'', y) => Some (a :: q'', y) | None => Some ([], a) end with | Some (q', y) => Some (enqueue q' x, y) | None => Some (empty A, x) end with | Some _ => False | None => True end endsplit; reflexivity.A: Set
x: A[] = empty A /\ x = xA: Set
a: A
q: list A
IHq: forall x : A, match dequeue (enqueue q x) with | Some (qa, xa) => match match dequeue q with | Some (q', y) => Some (enqueue q' x, y) | None => Some (empty A, x) end with | Some (qb, xb) => qa = qb /\ xa = xb | None => False end | None => match match dequeue q with | Some (q', y) => Some (enqueue q' x, y) | None => Some (empty A, x) end with | Some _ => False | None => True end end
x: Amatch match match dequeue q with | Some (q'', y) => Some (a :: q'', y) | None => Some ([], a) end with | Some (q'', y) => Some (x :: q'', y) | None => Some ([], x) end with | Some (qa, xa) => match match match dequeue q with | Some (q'', y) => Some (a :: q'', y) | None => Some ([], a) end with | Some (q', y) => Some (enqueue q' x, y) | None => Some (empty A, x) end with | Some (qb, xb) => qa = qb /\ xa = xb | None => False end | None => match match match dequeue q with | Some (q'', y) => Some (a :: q'', y) | None => Some ([], a) end with | Some (q', y) => Some (enqueue q' x, y) | None => Some (empty A, x) end with | Some _ => False | None => True end endall: split; reflexivity. Qed. End ListQueue.A: Set
a: A
q: list A
t0: t A
a0: A
IHq: forall x : A, match dequeue (enqueue q x) with | Some (qa, xa) => qa = enqueue t0 x /\ xa = a0 | None => False end
x: Ax :: a :: t0 = enqueue (a :: t0) x /\ a0 = a0A: Set
a: A
q: list A
IHq: forall x : A, match dequeue (enqueue q x) with | Some (qa, xa) => qa = empty A /\ xa = x | None => False end
x: A[x] = enqueue [] x /\ a = a
However, now we can implement the classic two-stacks optimized queue!
Module TwoStacksQueue : QUEUE.
Every queue is a pair of stacks: one for enqueuing and one for
dequeuing. The first stack, EnqueueHere
, has more recently
enqueued elements closer to the front, making enqueuing constant-time.
The second stack, DequeueHere
, has least recently enqueued elements
closer to the front, making dequeuing constant-time.
Record stackpair {A : Set} := { EnqueueHere : list A; DequeueHere : list A }. Arguments stackpair: clear implicits.
What's the catch? Sometimes we need to reverse EnqueueHere
and
transfer it to DequeueHere
, or otherwise there would never be anything
to dequeue! Luckily, the work we do in transfering is bounded
asymptotically by the total number of enqueue/dequeue operations, so
we get amortized constant time.
By the way, the Record
feature we used above is similar to e.g. structs
in C.
Definition t := stackpair. Definition empty A : t A := {| EnqueueHere := []; DequeueHere := [] |}. Definition enqueue {A} (q : t A) (x : A) : t A := {| EnqueueHere := x :: q.(EnqueueHere); DequeueHere := q.(DequeueHere) |}. Definition dequeue {A} (q : t A) : option (t A * A) := match q.(DequeueHere) with | x :: dq => Some ({| EnqueueHere := q.(EnqueueHere); DequeueHere := dq |}, x) | [] => (* Ran out of dequeuable elements. Reverse enqueued elements and transfer to the other stack. *) match rev q.(EnqueueHere) with | [] => None | x :: eq => Some ({| EnqueueHere := []; DequeueHere := eq |}, x) end end.
This function explains which simple queue representation we have in mind, for each fancy two-stack representation.
Definition elements {A} (q : t A) : list A := q.(EnqueueHere) ++ rev q.(DequeueHere).
That function is useful to define our equivalence relation.
Definition equiv {A} (a b : t A) := elements a = elements b. Infix "~=" := equiv (at level 70).forall (A : Set) (a : t A), a ~= acongruence. Qed.forall (A : Set) (a : t A), a ~= aforall (A : Set) (a b : t A), a ~= b -> b ~= acongruence. Qed.forall (A : Set) (a b : t A), a ~= b -> b ~= aforall (A : Set) (a b c : t A), a ~= b -> b ~= c -> a ~= ccongruence. Qed.forall (A : Set) (a b c : t A), a ~= b -> b ~= c -> a ~= c
Now it is mostly routine to prove the laws, though a few tricks may be worth reading through.
forall (A : Set) (a b : t A) (x : A), a ~= b -> enqueue a x ~= enqueue b xforall (A : Set) (a b : t A) (x : A), a ~= b -> enqueue a x ~= enqueue b xcongruence. Qed. Definition dequeue_equiv {A} (a b : option (t A * A)) := match a, b with | None, None => True | Some (qa, xa), Some (qb, xb) => qa ~= qb /\ xa = xb | _, _ => False end. Infix "~~=" := dequeue_equiv (at level 70).forall (A : Set) (a b : t A) (x : A), EnqueueHere a ++ rev (DequeueHere a) = EnqueueHere b ++ rev (DequeueHere b) -> x :: EnqueueHere a ++ rev (DequeueHere a) = x :: EnqueueHere b ++ rev (DequeueHere b)
The following proof offers an interesting example of automation.
Mindlessly destructing the discriminands of the match
es that
appear in the goal works, but ends up destructing too much: we end
up with having to reason about the result of destructing l
and
rev l
simultaneously.
Instead, the proof below starts by using properties of rev
to align
the hypotheses with the terms in the goal:
forall (A : Set) (a b : t A), a ~= b -> dequeue a ~~= dequeue bforall (A : Set) (a b : t A), a ~= b -> dequeue a ~~= dequeue bA: Set
a, b: t A
H: EnqueueHere a ++ rev (DequeueHere a) = EnqueueHere b ++ rev (DequeueHere b)match match DequeueHere a with | [] => match rev (EnqueueHere a) with | [] => None | x :: eq => Some ({| EnqueueHere := []; DequeueHere := eq |}, x) end | x :: dq => Some ({| EnqueueHere := EnqueueHere a; DequeueHere := dq |}, x) end with | Some (qa, xa) => match match DequeueHere b with | [] => match rev (EnqueueHere b) with | [] => None | x :: eq => Some ({| EnqueueHere := []; DequeueHere := eq |}, x) end | x :: dq => Some ({| EnqueueHere := EnqueueHere b; DequeueHere := dq |}, x) end with | Some (qb, xb) => EnqueueHere qa ++ rev (DequeueHere qa) = EnqueueHere qb ++ rev (DequeueHere qb) /\ xa = xb | None => False end | None => match match DequeueHere b with | [] => match rev (EnqueueHere b) with | [] => None | x :: eq => Some ({| EnqueueHere := []; DequeueHere := eq |}, x) end | x :: dq => Some ({| EnqueueHere := EnqueueHere b; DequeueHere := dq |}, x) end with | Some _ => False | None => True end end
Notice that at this point the goal mentions DequeueHere a
,
whereas the hypothesis mentions rev (DequeueHere a)
,
and vice-versa for EnqueueHere
.
A: Set
a, b: t A
H: DequeueHere a ++ rev (EnqueueHere a) = DequeueHere b ++ rev (EnqueueHere b)match match DequeueHere a with | [] => match rev (EnqueueHere a) with | [] => None | x :: eq => Some ({| EnqueueHere := []; DequeueHere := eq |}, x) end | x :: dq => Some ({| EnqueueHere := EnqueueHere a; DequeueHere := dq |}, x) end with | Some (qa, xa) => match match DequeueHere b with | [] => match rev (EnqueueHere b) with | [] => None | x :: eq => Some ({| EnqueueHere := []; DequeueHere := eq |}, x) end | x :: dq => Some ({| EnqueueHere := EnqueueHere b; DequeueHere := dq |}, x) end with | Some (qb, xb) => EnqueueHere qa ++ rev (DequeueHere qa) = EnqueueHere qb ++ rev (DequeueHere qb) /\ xa = xb | None => False end | None => match match DequeueHere b with | [] => match rev (EnqueueHere b) with | [] => None | x :: eq => Some ({| EnqueueHere := []; DequeueHere := eq |}, x) end | x :: dq => Some ({| EnqueueHere := EnqueueHere b; DequeueHere := dq |}, x) end with | Some _ => False | None => True end end
Aligning the two using properties of rev
makes the proof much simpler:
A: Set
a, b: t A
l: list A
a1: A
Heql: DequeueHere b = a1 :: l
l0: list A
H: l0 ++ rev (EnqueueHere a) = l ++ rev (EnqueueHere b)
Heql0: DequeueHere a = a1 :: l0EnqueueHere a ++ rev l0 = EnqueueHere b ++ rev l
After running a very simple automated solver, we are left with
just one goal that requires us to undo the rev
trick that we
previously applied:
A: Set
a, b: t A
l: list A
a1: A
Heql: DequeueHere b = a1 :: l
l0: list A
H: l0 ++ rev (EnqueueHere a) = l ++ rev (EnqueueHere b)
Heql0: DequeueHere a = a1 :: l0EnqueueHere a ++ rev l0 = EnqueueHere b ++ rev lassumption. } Qed.A: Set
a, b: t A
l: list A
a1: A
Heql: DequeueHere b = a1 :: l
l0: list A
H: EnqueueHere a ++ rev l0 = EnqueueHere b ++ rev l
Heql0: DequeueHere a = a1 :: l0EnqueueHere a ++ rev l0 = EnqueueHere b ++ rev lforall A : Set, dequeue (empty A) = Nonereflexivity. Qed.forall A : Set, dequeue (empty A) = None
A similar technique is applicable here:
forall (A : Type) (l1 l2 : list A), rev l1 = rev l2 -> l1 = l2forall (A : Type) (l1 l2 : list A), rev l1 = rev l2 -> l1 = l2A: Type
l1, l2: list A
H: rev l1 = rev l2l1 = l2congruence. Qed.A: Type
l1, l2: list A
H: rev l1 = rev l2rev (rev l1) = rev (rev l2)forall (A : Set) (q : t A), dequeue q = None -> q ~= empty Aforall (A : Set) (q : t A), dequeue q = None -> q ~= empty AA: Set
EH, DH: list A
H: match DH with | [] => match rev EH with | [] => None | x :: eq => Some ({| EnqueueHere := []; DequeueHere := eq |}, x) end | x :: dq => Some ({| EnqueueHere := EH; DequeueHere := dq |}, x) end = NoneEH ++ rev DH = []A: Set
EH, DH: list A
H: match DH with | [] => match rev EH with | [] => None | x :: eq => Some ({| EnqueueHere := []; DequeueHere := eq |}, x) end | x :: dq => Some ({| EnqueueHere := EH; DequeueHere := dq |}, x) end = NoneDH ++ rev EH = rev []all: congruence. Qed.A: Set
EH: list A
H: None = None[] = []A: Set
EH: list A
a: A
l: list A
H: Some ({| EnqueueHere := []; DequeueHere := l |}, a) = Nonea :: l = []A: Set
EH: list A
a: A
DH: list A
H: Some ({| EnqueueHere := EH; DequeueHere := DH |}, a) = Nonea :: DH ++ [] = []A: Set
EH: list A
a: A
DH: list A
H: Some ({| EnqueueHere := EH; DequeueHere := DH |}, a) = None
a0: A
l: list Aa :: DH ++ a0 :: l = []forall (A : Set) (q : t A) (x : A), dequeue (enqueue q x) ~~= match dequeue q with | Some (q', y) => Some (enqueue q' x, y) | None => Some (empty A, x) endforall (A : Set) (q : t A) (x : A), dequeue (enqueue q x) ~~= match dequeue q with | Some (q', y) => Some (enqueue q' x, y) | None => Some (empty A, x) endA: Set
EH, DH: list A
x: Amatch match DH with | [] => match rev EH ++ [x] with | [] => None | x :: eq => Some ({| EnqueueHere := []; DequeueHere := eq |}, x) end | x0 :: dq => Some ({| EnqueueHere := x :: EH; DequeueHere := dq |}, x0) end with | Some (qa, xa) => match match match DH with | [] => match rev EH with | [] => None | x :: eq => Some ({| EnqueueHere := []; DequeueHere := eq |}, x) end | x :: dq => Some ({| EnqueueHere := EH; DequeueHere := dq |}, x) end with | Some (q', y) => Some ({| EnqueueHere := x :: EnqueueHere q'; DequeueHere := DequeueHere q' |}, y) | None => Some ({| EnqueueHere := []; DequeueHere := [] |}, x) end with | Some (qb, xb) => EnqueueHere qa ++ rev (DequeueHere qa) = EnqueueHere qb ++ rev (DequeueHere qb) /\ xa = xb | None => False end | None => match match match DH with | [] => match rev EH with | [] => None | x :: eq => Some ({| EnqueueHere := []; DequeueHere := eq |}, x) end | x :: dq => Some ({| EnqueueHere := EH; DequeueHere := dq |}, x) end with | Some (q', y) => Some ({| EnqueueHere := x :: EnqueueHere q'; DequeueHere := DequeueHere q' |}, y) | None => Some ({| EnqueueHere := []; DequeueHere := [] |}, x) end with | Some _ => False | None => True end endA: Set
EH: list A
x: A[] = []A: Set
EH: list A
x: Ax = xA: Set
EH: list A
x, a: A
l: list Arev (l ++ [x]) = x :: rev lA: Set
EH: list A
x, a: A
l: list Aa = aA: Set
EH: list A
a: A
DH: list A
x: Ax :: EH ++ rev DH = x :: EH ++ rev DHA: Set
EH: list A
a: A
DH: list A
x: Aa = aA: Set
EH: list A
a: A
DH: list A
x, a0: A
l: list Ax :: EH ++ rev DH = x :: EH ++ rev DHA: Set
EH: list A
a: A
DH: list A
x, a0: A
l: list Aa = aall: reflexivity. Qed. End TwoStacksQueue.A: Set
EH: list A
x: A[] = []A: Set
EH: list A
x: Ax = xA: Set
EH: list A
x, a: A
l: list Ax :: rev l = x :: rev lA: Set
EH: list A
x, a: A
l: list Aa = aA: Set
EH: list A
a: A
DH: list A
x: Ax :: EH ++ rev DH = x :: EH ++ rev DHA: Set
EH: list A
a: A
DH: list A
x: Aa = aA: Set
EH: list A
a: A
DH: list A
x, a0: A
l: list Ax :: EH ++ rev DH = x :: EH ++ rev DHA: Set
EH: list A
a: A
DH: list A
x, a0: A
l: list Aa = a
The exercise of the generic delayed sum may be repeated with equivalence relations.
Module DelayedSum (Q : QUEUE). Fixpoint makeQueue (n : nat) (q : Q.t nat) : Q.t nat := match n with | 0 => q | S n' => makeQueue n' (Q.enqueue q n') end. Fixpoint computeSum (n : nat) (q : Q.t nat) : nat := match n with | 0 => 0 | S n' => match Q.dequeue q with | None => 0 | Some (q', v) => v + computeSum n' q' end end. Fixpoint sumUpto (n : nat) : nat := match n with | 0 => 0 | S n' => n' + sumUpto n' end. Infix "~=" := Q.equiv (at level 70). Infix "~~=" := Q.dequeue_equiv (at level 70).forall (n : nat) (a b : Q.t nat), a ~= b -> makeQueue n a ~= makeQueue n bforall (n : nat) (a b : Q.t nat), a ~= b -> makeQueue n a ~= makeQueue n ba, b: Q.t nat
H: a ~= bmakeQueue 0 a ~= makeQueue 0 bn: nat
IHn: forall a b : Q.t nat, a ~= b -> makeQueue n a ~= makeQueue n b
a, b: Q.t nat
H: a ~= bmakeQueue (S n) a ~= makeQueue (S n) bassumption.a, b: Q.t nat
H: a ~= bmakeQueue 0 a ~= makeQueue 0 bn: nat
IHn: forall a b : Q.t nat, a ~= b -> makeQueue n a ~= makeQueue n b
a, b: Q.t nat
H: a ~= bmakeQueue (S n) a ~= makeQueue (S n) bn: nat
IHn: forall a b : Q.t nat, a ~= b -> makeQueue n a ~= makeQueue n b
a, b: Q.t nat
H: a ~= bQ.enqueue a n ~= Q.enqueue b nassumption. Qed.n: nat
IHn: forall a b : Q.t nat, a ~= b -> makeQueue n a ~= makeQueue n b
a, b: Q.t nat
H: a ~= ba ~= bforall (n : nat) (q : Q.t nat), Q.dequeue (makeQueue n q) ~~= match Q.dequeue q with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' q, n') end endforall (n : nat) (q : Q.t nat), Q.dequeue (makeQueue n q) ~~= match Q.dequeue q with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' q, n') end endq: Q.t natQ.dequeue q ~~= match Q.dequeue q with | Some (q', v) => Some (q', v) | None => None endn: nat
IHn: forall q : Q.t nat, Q.dequeue (makeQueue n q) ~~= match Q.dequeue q with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' q, n') end end
q: Q.t natQ.dequeue (makeQueue n (Q.enqueue q n)) ~~= match Q.dequeue q with | Some (q', v) => Some (makeQueue n (Q.enqueue q' n), v) | None => Some (makeQueue n q, n) endq: Q.t natQ.dequeue q ~~= match Q.dequeue q with | Some (q', v) => Some (q', v) | None => None endq, t: Q.t nat
n: natt ~= t /\ n = nq: Q.t natTruesplit; [ apply Q.equiv_refl | reflexivity ].q, t: Q.t nat
n: natt ~= t /\ n = ntauto.q: Q.t natTruen: nat
IHn: forall q : Q.t nat, Q.dequeue (makeQueue n q) ~~= match Q.dequeue q with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' q, n') end end
q: Q.t natQ.dequeue (makeQueue n (Q.enqueue q n)) ~~= match Q.dequeue q with | Some (q', v) => Some (makeQueue n (Q.enqueue q' n), v) | None => Some (makeQueue n q, n) endn: nat
q: Q.t nat
IHn: Q.dequeue (makeQueue n (Q.enqueue q n)) ~~= match Q.dequeue (Q.enqueue q n) with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' (Q.enqueue q n), n') end endQ.dequeue (makeQueue n (Q.enqueue q n)) ~~= match Q.dequeue q with | Some (q', v) => Some (makeQueue n (Q.enqueue q' n), v) | None => Some (makeQueue n q, n) endn: nat
q: Q.t nat
IHn: Q.dequeue (makeQueue n (Q.enqueue q n)) ~~= match Q.dequeue (Q.enqueue q n) with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' (Q.enqueue q n), n') end end
Hde: Q.dequeue (Q.enqueue q n) ~~= match Q.dequeue q with | Some (q', y) => Some (Q.enqueue q' n, y) | None => Some (Q.empty nat, n) endQ.dequeue (makeQueue n (Q.enqueue q n)) ~~= match Q.dequeue q with | Some (q', v) => Some (makeQueue n (Q.enqueue q' n), v) | None => Some (makeQueue n q, n) endn: nat
q, t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
IHn: t0 ~= makeQueue n t /\ n1 = n0
t1: Q.t nat
n2: nat
Hdq: Q.dequeue q = Some (t1, n2)
Hde: t ~= Q.enqueue t1 n /\ n0 = n2t0 ~= makeQueue n (Q.enqueue t1 n) /\ n1 = n2n: nat
q, t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
IHn: t0 ~= makeQueue n t /\ n1 = n0
Hdq: Q.dequeue q = None
Hde: t ~= Q.empty nat /\ n0 = nt0 ~= makeQueue n q /\ n1 = nn: nat
q, t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
IHeqv: t0 ~= makeQueue n t
IHeq: n1 = n0
t1: Q.t nat
n2: nat
Hdq: Q.dequeue q = Some (t1, n2)
Heqv: t ~= Q.enqueue t1 n
Heq: n0 = n2t0 ~= makeQueue n (Q.enqueue t1 n)n: nat
q, t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
IHeqv: t0 ~= makeQueue n t
IHeq: n1 = n0
Hdq: Q.dequeue q = None
Heqv: t ~= Q.empty nat
Heq: n0 = nt0 ~= makeQueue n qn: nat
q, t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
IHeqv: t0 ~= makeQueue n t
IHeq: n1 = n0
t1: Q.t nat
n2: nat
Hdq: Q.dequeue q = Some (t1, n2)
Heqv: t ~= Q.enqueue t1 n
Heq: n0 = n2t0 ~= makeQueue n (Q.enqueue t1 n)n: nat
q, t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
IHeqv: t0 ~= makeQueue n t
IHeq: n1 = n0
t1: Q.t nat
n2: nat
Hdq: Q.dequeue q = Some (t1, n2)
Heqv: t ~= Q.enqueue t1 n
Heq: n0 = n2t0 ~= makeQueue n tn: nat
q, t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
IHeqv: t0 ~= makeQueue n t
IHeq: n1 = n0
t1: Q.t nat
n2: nat
Hdq: Q.dequeue q = Some (t1, n2)
Heqv: t ~= Q.enqueue t1 n
Heq: n0 = n2makeQueue n t ~= makeQueue n (Q.enqueue t1 n)assumption.n: nat
q, t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
IHeqv: t0 ~= makeQueue n t
IHeq: n1 = n0
t1: Q.t nat
n2: nat
Hdq: Q.dequeue q = Some (t1, n2)
Heqv: t ~= Q.enqueue t1 n
Heq: n0 = n2t0 ~= makeQueue n tapply makeQueue_congruence; assumption.n: nat
q, t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
IHeqv: t0 ~= makeQueue n t
IHeq: n1 = n0
t1: Q.t nat
n2: nat
Hdq: Q.dequeue q = Some (t1, n2)
Heqv: t ~= Q.enqueue t1 n
Heq: n0 = n2makeQueue n t ~= makeQueue n (Q.enqueue t1 n)n: nat
q, t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
IHeqv: t0 ~= makeQueue n t
IHeq: n1 = n0
Hdq: Q.dequeue q = None
Heqv: t ~= Q.empty nat
Heq: n0 = nt0 ~= makeQueue n qn: nat
q, t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
IHeqv: t0 ~= makeQueue n t
IHeq: n1 = n0
Hdq: q ~= Q.empty nat
Heqv: t ~= Q.empty nat
Heq: n0 = nt0 ~= makeQueue n qn: nat
q, t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
IHeqv: t0 ~= makeQueue n t
IHeq: n1 = n0
Hdq: q ~= Q.empty nat
Heqv: t ~= Q.empty nat
Heq: n0 = nt0 ~= makeQueue n tn: nat
q, t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
IHeqv: t0 ~= makeQueue n t
IHeq: n1 = n0
Hdq: q ~= Q.empty nat
Heqv: t ~= Q.empty nat
Heq: n0 = nmakeQueue n t ~= makeQueue n qassumption.n: nat
q, t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
IHeqv: t0 ~= makeQueue n t
IHeq: n1 = n0
Hdq: q ~= Q.empty nat
Heqv: t ~= Q.empty nat
Heq: n0 = nt0 ~= makeQueue n tn: nat
q, t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
IHeqv: t0 ~= makeQueue n t
IHeq: n1 = n0
Hdq: q ~= Q.empty nat
Heqv: t ~= Q.empty nat
Heq: n0 = nmakeQueue n t ~= makeQueue n qn: nat
q, t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
IHeqv: t0 ~= makeQueue n t
IHeq: n1 = n0
Hdq: q ~= Q.empty nat
Heqv: t ~= Q.empty nat
Heq: n0 = nt ~= qn: nat
q, t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
IHeqv: t0 ~= makeQueue n t
IHeq: n1 = n0
Hdq: q ~= Q.empty nat
Heqv: t ~= Q.empty nat
Heq: n0 = nt ~= Q.empty natn: nat
q, t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
IHeqv: t0 ~= makeQueue n t
IHeq: n1 = n0
Hdq: q ~= Q.empty nat
Heqv: t ~= Q.empty nat
Heq: n0 = nQ.empty nat ~= qassumption.n: nat
q, t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
IHeqv: t0 ~= makeQueue n t
IHeq: n1 = n0
Hdq: q ~= Q.empty nat
Heqv: t ~= Q.empty nat
Heq: n0 = nt ~= Q.empty natn: nat
q, t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
IHeqv: t0 ~= makeQueue n t
IHeq: n1 = n0
Hdq: q ~= Q.empty nat
Heqv: t ~= Q.empty nat
Heq: n0 = nQ.empty nat ~= qassumption. Qed.n: nat
q, t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
IHeqv: t0 ~= makeQueue n t
IHeq: n1 = n0
Hdq: q ~= Q.empty nat
Heqv: t ~= Q.empty nat
Heq: n0 = nq ~= Q.empty natforall (n : nat) (a b : Q.t nat), a ~= b -> computeSum n a = computeSum n bforall (n : nat) (a b : Q.t nat), a ~= b -> computeSum n a = computeSum n ba, b: Q.t nat
Heqv: a ~= b0 = 0n: nat
IHn: forall a b : Q.t nat, a ~= b -> computeSum n a = computeSum n b
a, b: Q.t nat
Heqv: a ~= bmatch Q.dequeue a with | Some (q', v) => v + computeSum n q' | None => 0 end = match Q.dequeue b with | Some (q', v) => v + computeSum n q' | None => 0 endreflexivity.a, b: Q.t nat
Heqv: a ~= b0 = 0n: nat
IHn: forall a b : Q.t nat, a ~= b -> computeSum n a = computeSum n b
a, b: Q.t nat
Heqv: a ~= bmatch Q.dequeue a with | Some (q', v) => v + computeSum n q' | None => 0 end = match Q.dequeue b with | Some (q', v) => v + computeSum n q' | None => 0 endn: nat
IHn: forall a b : Q.t nat, a ~= b -> computeSum n a = computeSum n b
a, b: Q.t nat
Heqv: a ~= b
Heqd: Q.dequeue a ~~= Q.dequeue bmatch Q.dequeue a with | Some (q', v) => v + computeSum n q' | None => 0 end = match Q.dequeue b with | Some (q', v) => v + computeSum n q' | None => 0 endn: nat
IHn: forall a b : Q.t nat, a ~= b -> computeSum n a = computeSum n b
a, b: Q.t nat
Heqv: a ~= b
Heqd: match Q.dequeue a with | Some (qa, xa) => match Q.dequeue b with | Some (qb, xb) => qa ~= qb /\ xa = xb | None => False end | None => match Q.dequeue b with | Some _ => False | None => True end endmatch Q.dequeue a with | Some (q', v) => v + computeSum n q' | None => 0 end = match Q.dequeue b with | Some (q', v) => v + computeSum n q' | None => 0 endn: nat
IHn: forall a b : Q.t nat, a ~= b -> computeSum n a = computeSum n b
a, b: Q.t nat
Heqv: a ~= b
t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
Heqd: t ~= t0 /\ n0 = n1n0 + computeSum n t = n1 + computeSum n t0n: nat
IHn: forall a b : Q.t nat, a ~= b -> computeSum n a = computeSum n b
a, b: Q.t nat
Heqv: a ~= b
t: Q.t nat
n0: nat
Heqd: Falsen0 + computeSum n t = 0n: nat
IHn: forall a b : Q.t nat, a ~= b -> computeSum n a = computeSum n b
a, b: Q.t nat
Heqv: a ~= b
t: Q.t nat
n0: nat
Heqd: False0 = n0 + computeSum n tn: nat
IHn: forall a b : Q.t nat, a ~= b -> computeSum n a = computeSum n b
a, b: Q.t nat
Heqv: a ~= b
Heqd: True0 = 0n: nat
IHn: forall a b : Q.t nat, a ~= b -> computeSum n a = computeSum n b
a, b: Q.t nat
Heqv: a ~= b
t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
Heqd: t ~= t0 /\ n0 = n1n0 + computeSum n t = n1 + computeSum n t0n: nat
IHn: forall a b : Q.t nat, a ~= b -> computeSum n a = computeSum n b
a, b: Q.t nat
Heqv: a ~= b
t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
Heqv': t ~= t0
Heq: n0 = n1n0 + computeSum n t = n1 + computeSum n t0reflexivity.n: nat
IHn: forall a b : Q.t nat, a ~= b -> computeSum n a = computeSum n b
a, b: Q.t nat
Heqv: a ~= b
t: Q.t nat
n0: nat
t0: Q.t nat
n1: nat
Heqv': t ~= t0
Heq: n0 = n1n1 + computeSum n t0 = n1 + computeSum n t0exfalso; assumption.n: nat
IHn: forall a b : Q.t nat, a ~= b -> computeSum n a = computeSum n b
a, b: Q.t nat
Heqv: a ~= b
t: Q.t nat
n0: nat
Heqd: Falsen0 + computeSum n t = 0exfalso; assumption.n: nat
IHn: forall a b : Q.t nat, a ~= b -> computeSum n a = computeSum n b
a, b: Q.t nat
Heqv: a ~= b
t: Q.t nat
n0: nat
Heqd: False0 = n0 + computeSum n treflexivity. Qed.n: nat
IHn: forall a b : Q.t nat, a ~= b -> computeSum n a = computeSum n b
a, b: Q.t nat
Heqv: a ~= b
Heqd: True0 = 0forall n : nat, computeSum n (makeQueue n (Q.empty nat)) = sumUpto nforall n : nat, computeSum n (makeQueue n (Q.empty nat)) = sumUpto n0 = 0n: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto nmatch Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) with | Some (q', v) => v + computeSum n q' | None => 0 end = n + sumUpto nreflexivity.0 = 0n: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto nmatch Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) with | Some (q', v) => v + computeSum n q' | None => 0 end = n + sumUpto nn: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto n
HdmQ: Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) ~~= match Q.dequeue (Q.enqueue (Q.empty nat) n) with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' (Q.enqueue (Q.empty nat) n), n') end endmatch Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) with | Some (q', v) => v + computeSum n q' | None => 0 end = n + sumUpto nn: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto n
HdmQ: Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) ~~= match Q.dequeue (Q.enqueue (Q.empty nat) n) with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' (Q.enqueue (Q.empty nat) n), n') end end
Hdev: Q.dequeue (Q.enqueue (Q.empty nat) n) ~~= match Q.dequeue (Q.empty nat) with | Some (q', y) => Some (Q.enqueue q' n, y) | None => Some (Q.empty nat, n) endmatch Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) with | Some (q', v) => v + computeSum n q' | None => 0 end = n + sumUpto nn: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto n
HdmQ: match Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) with | Some (qa, xa) => match match Q.dequeue (Q.enqueue (Q.empty nat) n) with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' (Q.enqueue (Q.empty nat) n), n') end end with | Some (qb, xb) => qa ~= qb /\ xa = xb | None => False end | None => match match Q.dequeue (Q.enqueue (Q.empty nat) n) with | Some (q', v) => Some (makeQueue n q', v) | None => match n with | 0 => None | S n' => Some (makeQueue n' (Q.enqueue (Q.empty nat) n), n') end end with | Some _ => False | None => True end end
Hdev: match Q.dequeue (Q.enqueue (Q.empty nat) n) with | Some (qa, xa) => match match Q.dequeue (Q.empty nat) with | Some (q', y) => Some (Q.enqueue q' n, y) | None => Some (Q.empty nat, n) end with | Some (qb, xb) => qa ~= qb /\ xa = xb | None => False end | None => match match Q.dequeue (Q.empty nat) with | Some (q', y) => Some (Q.enqueue q' n, y) | None => Some (Q.empty nat, n) end with | Some _ => False | None => True end endmatch Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) with | Some (q', v) => v + computeSum n q' | None => 0 end = n + sumUpto nn: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto n
t: Q.t nat
n0: nat
Hdm: Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) = Some (t, n0)
t0: Q.t nat
n1: nat
Hde: Q.dequeue (Q.enqueue (Q.empty nat) n) = Some (t0, n1)
HdmQ: t ~= makeQueue n t0 /\ n0 = n1
Hdev: match match Q.dequeue (Q.empty nat) with | Some (q', y) => Some (Q.enqueue q' n, y) | None => Some (Q.empty nat, n) end with | Some (qb, xb) => t0 ~= qb /\ n1 = xb | None => False endn0 + computeSum n t = n + sumUpto nn: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto n
t: Q.t nat
n0: nat
Hdm: Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) = Some (t, n0)
Hde: Q.dequeue (Q.enqueue (Q.empty nat) n) = None
HdmQ: match match n with | 0 => None | S n' => Some (makeQueue n' (Q.enqueue (Q.empty nat) n), n') end with | Some (qb, xb) => t ~= qb /\ n0 = xb | None => False end
Hdev: match match Q.dequeue (Q.empty nat) with | Some (q', y) => Some (Q.enqueue q' n, y) | None => Some (Q.empty nat, n) end with | Some _ => False | None => True endn0 + computeSum n t = n + sumUpto nn: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto n
Hdm: Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) = None
t: Q.t nat
n0: nat
Hde: Q.dequeue (Q.enqueue (Q.empty nat) n) = Some (t, n0)
HdmQ: False
Hdev: match match Q.dequeue (Q.empty nat) with | Some (q', y) => Some (Q.enqueue q' n, y) | None => Some (Q.empty nat, n) end with | Some (qb, xb) => t ~= qb /\ n0 = xb | None => False end0 = n + sumUpto nn: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto n
Hdm: Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) = None
Hde: Q.dequeue (Q.enqueue (Q.empty nat) n) = None
HdmQ: match match n with | 0 => None | S n' => Some (makeQueue n' (Q.enqueue (Q.empty nat) n), n') end with | Some _ => False | None => True end
Hdev: match match Q.dequeue (Q.empty nat) with | Some (q', y) => Some (Q.enqueue q' n, y) | None => Some (Q.empty nat, n) end with | Some _ => False | None => True end0 = n + sumUpto nn: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto n
t: Q.t nat
n0: nat
Hdm: Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) = Some (t, n0)
t0: Q.t nat
n1: nat
Hde: Q.dequeue (Q.enqueue (Q.empty nat) n) = Some (t0, n1)
HdmQ: t ~= makeQueue n t0 /\ n0 = n1
Hdev: t0 ~= Q.empty nat /\ n1 = nn0 + computeSum n t = n + sumUpto nn: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto n
t: Q.t nat
n0: nat
Hdm: Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) = Some (t, n0)
Hde: Q.dequeue (Q.enqueue (Q.empty nat) n) = None
HdmQ: match match n with | 0 => None | S n' => Some (makeQueue n' (Q.enqueue (Q.empty nat) n), n') end with | Some (qb, xb) => t ~= qb /\ n0 = xb | None => False end
Hdev: Falsen0 + computeSum n t = n + sumUpto nn: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto n
Hdm: Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) = None
t: Q.t nat
n0: nat
Hde: Q.dequeue (Q.enqueue (Q.empty nat) n) = Some (t, n0)
HdmQ: False
Hdev: t ~= Q.empty nat /\ n0 = n0 = n + sumUpto nn: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto n
Hdm: Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) = None
Hde: Q.dequeue (Q.enqueue (Q.empty nat) n) = None
HdmQ: match match n with | 0 => None | S n' => Some (makeQueue n' (Q.enqueue (Q.empty nat) n), n') end with | Some _ => False | None => True end
Hdev: False0 = n + sumUpto nn: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto n
t: Q.t nat
n0: nat
Hdm: Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) = Some (t, n0)
t0: Q.t nat
n1: nat
Hde: Q.dequeue (Q.enqueue (Q.empty nat) n) = Some (t0, n1)
HdmQ: t ~= makeQueue n t0 /\ n0 = n1
Hdev: t0 ~= Q.empty nat /\ n1 = nn0 + computeSum n t = n + sumUpto nn: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto n
t: Q.t nat
n0: nat
Hdm: Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) = Some (t, n0)
t0: Q.t nat
n1: nat
Hde: Q.dequeue (Q.enqueue (Q.empty nat) n) = Some (t0, n1)
HdmQ: t ~= makeQueue n t0 /\ n0 = n1
Hdev: t0 ~= Q.empty nat /\ n1 = nn0 + computeSum n t = n + sumUpto nn: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto n
t: Q.t nat
Hdm: Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) = Some (t, n)
t0: Q.t nat
Hde: Q.dequeue (Q.enqueue (Q.empty nat) n) = Some (t0, n)
Heqv: t ~= makeQueue n t0
Hdeqv: t0 ~= Q.empty natn + computeSum n t = n + sumUpto nn: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto n
t: Q.t nat
Hdm: Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) = Some (t, n)
t0: Q.t nat
Hde: Q.dequeue (Q.enqueue (Q.empty nat) n) = Some (t0, n)
Heqv: t ~= makeQueue n t0
Hdeqv: t0 ~= Q.empty natn + computeSum n t = n + computeSum n (makeQueue n (Q.empty nat))n: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto n
t: Q.t nat
Hdm: Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) = Some (t, n)
t0: Q.t nat
Hde: Q.dequeue (Q.enqueue (Q.empty nat) n) = Some (t0, n)
Heqv: t ~= makeQueue n t0
Hdeqv: t0 ~= Q.empty natcomputeSum n t = computeSum n (makeQueue n (Q.empty nat))n: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto n
t: Q.t nat
Hdm: Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) = Some (t, n)
t0: Q.t nat
Hde: Q.dequeue (Q.enqueue (Q.empty nat) n) = Some (t0, n)
Heqv: t ~= makeQueue n t0
Hdeqv: t0 ~= Q.empty natt ~= makeQueue n (Q.empty nat)n: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto n
t: Q.t nat
Hdm: Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) = Some (t, n)
t0: Q.t nat
Hde: Q.dequeue (Q.enqueue (Q.empty nat) n) = Some (t0, n)
Heqv: t ~= makeQueue n t0
Hdeqv: t0 ~= Q.empty natt ~= makeQueue n t0n: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto n
t: Q.t nat
Hdm: Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) = Some (t, n)
t0: Q.t nat
Hde: Q.dequeue (Q.enqueue (Q.empty nat) n) = Some (t0, n)
Heqv: t ~= makeQueue n t0
Hdeqv: t0 ~= Q.empty natmakeQueue n t0 ~= makeQueue n (Q.empty nat)assumption.n: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto n
t: Q.t nat
Hdm: Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) = Some (t, n)
t0: Q.t nat
Hde: Q.dequeue (Q.enqueue (Q.empty nat) n) = Some (t0, n)
Heqv: t ~= makeQueue n t0
Hdeqv: t0 ~= Q.empty natt ~= makeQueue n t0n: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto n
t: Q.t nat
Hdm: Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) = Some (t, n)
t0: Q.t nat
Hde: Q.dequeue (Q.enqueue (Q.empty nat) n) = Some (t0, n)
Heqv: t ~= makeQueue n t0
Hdeqv: t0 ~= Q.empty natmakeQueue n t0 ~= makeQueue n (Q.empty nat)assumption. } Qed. End DelayedSum. End AlgebraicWithEquivalenceRelation.n: nat
IHn: computeSum n (makeQueue n (Q.empty nat)) = sumUpto n
t: Q.t nat
Hdm: Q.dequeue (makeQueue n (Q.enqueue (Q.empty nat) n)) = Some (t, n)
t0: Q.t nat
Hde: Q.dequeue (Q.enqueue (Q.empty nat) n) = Some (t0, n)
Heqv: t ~= makeQueue n t0
Hdeqv: t0 ~= Q.empty natt0 ~= Q.empty nat
Specifications with representation functions
It's worth presenting one final style of data-abstraction formalism: we introduce representation functions in the interface, to map the internal representation to some standard one that is easy to reason about. We don't expect "real code" to call the representation function. Instead, it's just a proof device to let us write convincing laws. Here's the previous example redone in this manner, without comment.
Module RepFunction. Module Type QUEUE. Parameter t : Set -> Set. Parameter empty : forall A, t A. Parameter enqueue : forall {A}, t A -> A -> t A. Parameter dequeue : forall {A}, t A -> option (t A * A). Parameter rep : forall {A}, t A -> list A. Axiom empty_rep : forall {A}, rep (empty A) = []. Axiom enqueue_rep : forall {A} (q : t A) x, rep (enqueue q x) = x :: rep q. Axiom dequeue_empty : forall {A} (q : t A), rep q = [] -> dequeue q = None. Axiom dequeue_nonempty : forall {A} (q : t A) xs x, rep q = xs ++ [x] -> exists q', dequeue q = Some (q', x) /\ rep q' = xs. End QUEUE.
We leave the proofs of using this new style as exercises: