Skip to content

Commit 9593c18

Browse files
author
Alex Gryzlov
committed
WIP Maps
1 parent fed3357 commit 9593c18

File tree

1 file changed

+326
-0
lines changed

1 file changed

+326
-0
lines changed

src/Maps.lidr

Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
= Maps: Total and Partial Maps
2+
3+
Maps (or dictionaries) are ubiquitous data structures both generally and in the
4+
theory of programming languages in particular; we're going to need them in many
5+
places in the coming chapters. They also make a nice case study using ideas
6+
we've seen in previous chapters, including building data structures out of
7+
higher-order functions (from `Basics` and `Poly`) and the use of reflection to
8+
streamline proofs (from `IndProp`).
9+
10+
We'll define two flavors of maps: _total_ maps, which include a "default"
11+
element to be returned when a key being looked up doesn't exist, and _partial_
12+
maps, which return a \idr{Maybe} to indicate success or failure. The latter is
13+
defined in terms of the former, using \idr{Maybe} as the default element.
14+
15+
16+
== The Idris Standard Library
17+
18+
\todo[inline]{Edit}
19+
20+
One small digression before we get to maps.
21+
22+
Unlike the chapters we have seen so far, this one does not Require Import the
23+
chapter before it (and, transitively, all the earlier chapters). Instead, in
24+
this chapter and from now, on we're going to import the definitions and theorems
25+
we need directly from Idris's standard library stuff. You should not notice much
26+
difference, though, because we've been careful to name our own definitions and
27+
theorems the same as their counterparts in the standard library, wherever they
28+
overlap.
29+
30+
Require Import Idris.Arith.Arith.
31+
Require Import Idris.Bool.Bool.
32+
Require Import Idris.Strings.String.
33+
Require Import Idris.Logic.FunctionalExtensionality.
34+
35+
Documentation for the standard library can be found at
36+
http://Idris.inria.fr/library/.
37+
38+
The Search command is a good way to look for theorems involving objects of
39+
specific types. Take a minute now to experiment with it.
40+
41+
42+
== Identifiers
43+
44+
First, we need a type for the keys that we use to index into our maps. For this
45+
purpose, we again use the type \idr{Id} from the `Lists` chapter. To make this
46+
chapter self contained, we repeat its definition here, together with the
47+
equality comparison function for \idr{Id} and its fundamental property.
48+
49+
> data Id : Type where
50+
> MkId : String -> Id
51+
52+
> beq_id : (x1, x2 : Id) -> Bool
53+
> beq_id (MkId n1) (MkId n2) = decAsBool $ decEq n1 n2
54+
55+
\todo[inline]{Edit}
56+
57+
(The function \idr{decEq} comes from Idris's string library. If you check its
58+
result type, you'll see that it does not actually return a \idr{Bool}, but
59+
rather a type that looks like \idr{Either (x = y) (Not (x = y))}, called a
60+
{Dec}, which can be thought of as an "evidence-carrying boolean." Formally, an
61+
element of \idr{Dec (x=y)} is either a proof that two things are equal or a
62+
proof that they are unequal, together with a tag indicating which. But for
63+
present purposes you can think of it as just a fancy \idr{Bool}.)
64+
65+
> beq_id_refl : (x : Id) -> True = beq_id x x
66+
> beq_id_refl (MkId n) with (decEq n n)
67+
> beq_id_refl _ | Yes _ = Refl
68+
> beq_id_refl _ | No contra = absurd $ contra Refl
69+
70+
The following useful property of \idr{beq_id} follows from an analogous lemma
71+
about strings:
72+
73+
\todo[inline]{Copypasted `<->` for now}
74+
75+
> iff : {p,q : Type} -> Type
76+
> iff {p} {q} = (p -> q, q -> p)
77+
78+
> syntax [p] "<->" [q] = iff {p} {q}
79+
80+
> bto : (beq_id x y = True) -> x = y
81+
> bto {x=MkId n1} {y=MkId n2} prf with (decEq n1 n2)
82+
> bto Refl | (Yes eq) = cong {f=MkId} eq
83+
> bto prf | (No _) = absurd prf
84+
> bfro : (x = y) -> beq_id x y = True
85+
> bfro {x=MkId n1} {y=MkId n2} prf with (decEq n1 n2)
86+
> bfro _ | (Yes _) = Refl
87+
> bfro prf | (No contra) = absurd $ contra $ idInj prf
88+
> where
89+
> idInj : MkId x = MkId y -> x = y
90+
> idInj Refl = Refl
91+
92+
> beq_id_true_iff : (beq_id x y = True) <-> x = y
93+
> beq_id_true_iff = (bto, bfro)
94+
95+
Similarly:
96+
97+
\todo[inline]{Copypasted here for now}
98+
99+
> not_true_and_false : (b = False) -> (b = True) -> Void
100+
> not_true_and_false {b=False} _ Refl impossible
101+
> not_true_and_false {b=True} Refl _ impossible
102+
> not_true_is_false : Not (b = True) -> b = False
103+
> not_true_is_false {b=False} h = Refl
104+
> not_true_is_false {b=True} h = absurd $ h Refl
105+
106+
> beq_id_false_iff : (beq_id x y = False) <-> Not (x = y)
107+
> beq_id_false_iff = (to, fro)
108+
> where
109+
> to : (beq_id x y = False) -> Not (x = y)
110+
> to beqf = not_true_and_false beqf . bfro
111+
> fro : (Not (x = y)) -> beq_id x y = False
112+
> fro noteq = not_true_is_false $ noteq . bto
113+
114+
115+
== Total Maps
116+
117+
Our main job in this chapter will be to build a definition of partial maps that
118+
is similar in behavior to the one we saw in the `Lists` chapter, plus
119+
accompanying lemmas about its behavior.
120+
121+
This time around, though, we're going to use _functions_, rather than lists of
122+
key-value pairs, to build maps. The advantage of this representation is that it
123+
offers a more _extensional_ view of maps, where two maps that respond to queries
124+
in the same way will be represented as literally the same thing (the very same
125+
function), rather than just "equivalent" data structures. This, in turn,
126+
simplifies proofs that use maps.
127+
128+
We build partial maps in two steps. First, we define a type of _total maps_ that
129+
return a default value when we look up a key that is not present in the map.
130+
131+
> total_map : Type -> Type
132+
> total_map a = Id -> a
133+
134+
Intuitively, a total map over an element type \idr{a} is just a function that
135+
can be used to look up \idr{Id}s, yielding \idr{a}s.
136+
137+
The function \idr{t_empty} yields an empty total map, given a default element;
138+
this map always returns the default element when applied to any id.
139+
140+
> t_empty : (v : a) -> total_map a
141+
> t_empty v = \_ => v
142+
143+
More interesting is the \idr{update} function, which (as before) takes a map
144+
\idr{m}, a key \idr{x}, and a value \idr{v} and returns a new map that takes
145+
\idr{x} to \idr{v} and takes every other key to whatever \idr{m} does.
146+
147+
> t_update : (x : Id) -> (v : a) -> (m : total_map a) -> total_map a
148+
> t_update x v m = \x' => if beq_id x x' then v else m x'
149+
150+
This definition is a nice example of higher-order programming: \idr{t_update}
151+
takes a _function_ \idr{m} and yields a new function \idr{\x' => ...} that
152+
behaves like the desired map.
153+
154+
For example, we can build a map taking \idr{Id}s to \idr{Bool}s, where \idr{Id
155+
3} is mapped to \idr{True} and every other key is mapped to \idr{False}, like
156+
this:
157+
158+
\todo[inline]{Seems like an inconsistency in the book here}
159+
160+
> examplemap : total_map Bool
161+
> examplemap = t_update (MkId "foo") False $
162+
> t_update (MkId "bar") True $
163+
> t_empty False
164+
165+
This completes the definition of total maps. Note that we don't need to define a
166+
\idr{find} operation because it is just function application!
167+
168+
> update_example1 : examplemap (MkId "baz") = False
169+
> update_example1 = Refl
170+
171+
> update_example2 : examplemap (MkId "foo") = False
172+
> update_example2 = Refl
173+
174+
> update_example3 : examplemap (MkId "quux") = False
175+
> update_example3 = Refl
176+
177+
> update_example4 : examplemap (MkId "bar") = True
178+
> update_example4 = Refl
179+
180+
To use maps in later chapters, we'll need several fundamental facts about how
181+
they behave. Even if you don't work the following exercises, make sure you
182+
thoroughly understand the statements of the lemmas! (Some of the proofs require
183+
the functional extensionality axiom, which is discussed in the `Logic` chapter.)
184+
185+
186+
==== Exercise: 1 star, optional (t_apply_empty)
187+
188+
First, the empty map returns its default element for all keys:
189+
190+
> t_apply_empty: t_empty v x = v
191+
> t_apply_empty = ?t_apply_empty_rhs
192+
193+
$\square$
194+
195+
196+
==== Exercise: 2 stars, optional (t_update_eq)
197+
198+
Next, if we update a map \idr{m} at a key \idr{x} with a new value v and then
199+
look up x in the map resulting from the update, we get back v:
200+
201+
Lemma t_update_eq : ∀A (m: total_map A) x v,
202+
(t_update m x v) x = v.
203+
Proof.
204+
(* FILL IN HERE *) Admitted.
205+
$\square$
206+
207+
208+
==== Exercise: 2 stars, optional (t_update_neq)
209+
210+
On the other hand, if we update a map m at a key x1 and then look up a different
211+
key x2 in the resulting map, we get the same result that m would have given:
212+
213+
Theorem t_update_neq : ∀(X:Type) v x1 x2
214+
(m : total_map X),
215+
x1 ≠ x2 →
216+
(t_update m x1 v) x2 = m x2.
217+
Proof.
218+
(* FILL IN HERE *) Admitted.
219+
$\square$
220+
221+
222+
==== Exercise: 2 stars, optional (t_update_shadow)
223+
224+
If we update a map m at a key x with a value v1 and then update again with the
225+
same key x and another value v2, the resulting map behaves the same (gives the
226+
same result when applied to any key) as the simpler map obtained by performing
227+
just the second update on m:
228+
229+
Lemma t_update_shadow : ∀A (m: total_map A) v1 v2 x,
230+
t_update (t_update m x v1) x v2
231+
= t_update m x v2.
232+
Proof.
233+
(* FILL IN HERE *) Admitted.
234+
235+
$\square$
236+
237+
For the final two lemmas about total maps, it's convenient to use the reflection
238+
idioms introduced in chapter IndProp. We begin by proving a fundamental
239+
reflection lemma relating the equality proposition on ids with the boolean
240+
function beq_id.
241+
242+
243+
==== Exercise: 2 stars, optional (beq_idP)
244+
245+
Use the proof of beq_natP in chapter IndProp as a template to prove the following:
246+
247+
Lemma beq_idP : ∀x y, reflect (x = y) (beq_id x y).
248+
Proof.
249+
(* FILL IN HERE *) Admitted.
250+
251+
$\square$
252+
253+
Now, given ids x1 and x2, we can use the destruct (beq_idP x1 x2) to
254+
simultaneously perform case analysis on the result of beq_id x1 x2 and generate
255+
hypotheses about the equality (in the sense of =) of x1 and x2.
256+
257+
258+
==== Exercise: 2 stars (t_update_same)
259+
260+
With the example in chapter IndProp as a template, use beq_idP to prove the
261+
following theorem, which states that if we update a map to assign key x the same
262+
value as it already has in m, then the result is equal to m:
263+
264+
Theorem t_update_same : ∀X x (m : total_map X),
265+
t_update m x (m x) = m.
266+
Proof.
267+
(* FILL IN HERE *) Admitted.
268+
269+
$\square$
270+
271+
272+
==== Exercise: 3 stars, recommended (t_update_permute)
273+
274+
Use beq_idP to prove one final property of the update function: If we update a
275+
map m at two distinct keys, it doesn't matter in which order we do the updates.
276+
277+
Theorem t_update_permute : ∀(X:Type) v1 v2 x1 x2
278+
(m : total_map X),
279+
x2 ≠ x1 →
280+
(t_update (t_update m x2 v2) x1 v1)
281+
= (t_update (t_update m x1 v1) x2 v2).
282+
Proof.
283+
(* FILL IN HERE *) Admitted.
284+
285+
$\square$
286+
287+
== Partial maps
288+
289+
Finally, we define partial maps on top of total maps. A partial map with
290+
elements of type A is simply a total map with elements of type option A and
291+
default element None.
292+
293+
Definition partial_map (A:Type) := total_map (option A).
294+
295+
Definition empty {A:Type} : partial_map A :=
296+
t_empty None.
297+
298+
Definition update {A:Type} (m : partial_map A)
299+
(x : id) (v : A) :=
300+
t_update m x (Some v).
301+
302+
We now straightforwardly lift all of the basic lemmas about total maps to
303+
partial maps.
304+
305+
Lemma apply_empty : ∀A x, @empty A x = None.
306+
307+
Lemma update_eq : ∀A (m: partial_map A) x v,
308+
(update m x v) x = Some v.
309+
310+
Theorem update_neq : ∀(X:Type) v x1 x2
311+
(m : partial_map X),
312+
x2 ≠ x1 →
313+
(update m x2 v) x1 = m x1.
314+
315+
Lemma update_shadow : ∀A (m: partial_map A) v1 v2 x,
316+
update (update m x v1) x v2 = update m x v2.
317+
318+
Theorem update_same : ∀X v x (m : partial_map X),
319+
m x = Some v →
320+
update m x v = m.
321+
322+
Theorem update_permute : ∀(X:Type) v1 v2 x1 x2
323+
(m : partial_map X),
324+
x2 ≠ x1 →
325+
(update (update m x2 v2) x1 v1)
326+
= (update (update m x1 v1) x2 v2).

0 commit comments

Comments
 (0)