|
| 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