MapsTotal and Partial Maps
From Coq Require Import Arith.
From Coq Require Import Bool.
From Coq Require Export Strings.String.
From Coq Require Import FunctionalExtensionality.
From Coq Require Import List.
Import ListNotations.
Set Default Goal Selector "!".
From Coq Require Import Bool.
From Coq Require Export Strings.String.
From Coq Require Import FunctionalExtensionality.
From Coq Require Import List.
Import ListNotations.
Set Default Goal Selector "!".
If you want to find out how or where a notation is defined, the
Locate command is useful. For example, where is the natural
addition operation defined in the standard library?
Locate "+".
(There are several uses of the + notation, but only one for
naturals.)
Print Init.Nat.add.
We'll see some more uses of Locate in the Imp chapter.
Identifiers
Check String.eqb_refl :
∀ x : string, (x =? x)%string = true.
∀ x : string, (x =? x)%string = true.
Check String.eqb_eq :
∀ n m : string, (n =? m)%string = true ↔ n = m.
Check String.eqb_neq :
∀ n m : string, (n =? m)%string = false ↔ n ≠ m.
Check String.eqb_spec :
∀ x y : string, reflect (x = y) (String.eqb x y).
∀ n m : string, (n =? m)%string = true ↔ n = m.
Check String.eqb_neq :
∀ n m : string, (n =? m)%string = false ↔ n ≠ m.
Check String.eqb_spec :
∀ x y : string, reflect (x = y) (String.eqb x y).
Total Maps
We build up to partial maps in two steps. First, we define a type of total maps that return a default value when we look up a key that is not present in the map.
Definition total_map (A : Type) := string → A.
Intuitively, a total map over an element type A is just a
function that can be used to look up strings, yielding As.
The function t_empty yields an empty total map, given a default element; this map always returns the default element when applied to any string.
Definition t_empty {A : Type} (v : A) : total_map A :=
(fun _ ⇒ v).
(fun _ ⇒ v).
More interesting is the map-updating function, which (as always) takes a map m, a key x, and a value v and returns a new map that takes x to v and takes every other key to whatever m does. The novelty here is that we achieve this effect by wrapping a new function around the old one.
Definition t_update {A : Type} (m : total_map A)
(x : string) (v : A) :=
fun x' ⇒ if String.eqb x x' then v else m x'.
(x : string) (v : A) :=
fun x' ⇒ if String.eqb x x' then v else m x'.
This definition is a nice example of higher-order programming:
t_update takes a function m and yields a new function
fun x' ⇒ ... that behaves like the desired map.
For example, we can build a map taking strings to bools, where "foo" and "bar" are mapped to true and every other key is mapped to false, like this:
Definition examplemap :=
t_update (t_update (t_empty false) "foo" true)
"bar" true.
t_update (t_update (t_empty false) "foo" true)
"bar" true.
Next, let's introduce some notations to facilitate working with maps.
Notation "'_' '!->' v" := (t_empty v)
(at level 100, right associativity).
Example example_empty := (_ !-> false).
(at level 100, right associativity).
Example example_empty := (_ !-> false).
Notation "x '!->' v ';' m" := (t_update m x v)
(at level 100, v at next level, right associativity).
(at level 100, v at next level, right associativity).
Definition examplemap' :=
( "bar" !-> true;
"foo" !-> true;
_ !-> false
).
( "bar" !-> true;
"foo" !-> true;
_ !-> false
).
When we use maps in later chapters, we'll need several fundamental facts about how they behave.
Lemma t_apply_empty : ∀ (A : Type) (x : string) (v : A),
(_ !-> v) x = v.
Lemma t_update_eq : ∀ (A : Type) (m : total_map A) x v,
(x !-> v ; m) x = v.
Theorem t_update_neq : ∀ (A : Type) (m : total_map A) x1 x2 v,
x1 ≠ x2 →
(x1 !-> v ; m) x2 = m x2.
Lemma t_update_shadow : ∀ (A : Type) (m : total_map A) x v1 v2,
(x !-> v2 ; x !-> v1 ; m) = (x !-> v2 ; m).
(_ !-> v) x = v.
Proof.
(* FILL IN HERE *) Admitted.
(* FILL IN HERE *) Admitted.
Lemma t_update_eq : ∀ (A : Type) (m : total_map A) x v,
(x !-> v ; m) x = v.
Proof.
(* FILL IN HERE *) Admitted.
(* FILL IN HERE *) Admitted.
Theorem t_update_neq : ∀ (A : Type) (m : total_map A) x1 x2 v,
x1 ≠ x2 →
(x1 !-> v ; m) x2 = m x2.
Proof.
(* FILL IN HERE *) Admitted.
(* FILL IN HERE *) Admitted.
Lemma t_update_shadow : ∀ (A : Type) (m : total_map A) x v1 v2,
(x !-> v2 ; x !-> v1 ; m) = (x !-> v2 ; m).
Proof.
(* FILL IN HERE *) Admitted.
(* FILL IN HERE *) Admitted.
Theorem t_update_same : ∀ (A : Type) (m : total_map A) x,
(x !-> m x ; m) = m.
Proof.
(* FILL IN HERE *) Admitted.
(* FILL IN HERE *) Admitted.
Theorem t_update_permute : ∀ (A : Type) (m : total_map A)
v1 v2 x1 x2,
x2 ≠ x1 →
(x1 !-> v1 ; x2 !-> v2 ; m)
=
(x2 !-> v2 ; x1 !-> v1 ; m).
Proof.
(* FILL IN HERE *) Admitted.
(* FILL IN HERE *) Admitted.
Partial maps
Definition partial_map (A : Type) := total_map (option A).
Definition empty {A : Type} : partial_map A :=
t_empty None.
Definition update {A : Type} (m : partial_map A)
(x : string) (v : A) :=
(x !-> Some v ; m).
Definition empty {A : Type} : partial_map A :=
t_empty None.
Definition update {A : Type} (m : partial_map A)
(x : string) (v : A) :=
(x !-> Some v ; m).
Notation "x '⊢>' v ';' m" := (update m x v)
(at level 100, v at next level, right associativity).
(at level 100, v at next level, right associativity).
We can also hide the last case when it is empty.
Notation "x '⊢>' v" := (update empty x v)
(at level 100).
Definition examplepmap :=
("Church" ⊢> true ; "Turing" ⊢> false).
(at level 100).
Definition examplepmap :=
("Church" ⊢> true ; "Turing" ⊢> false).
Lemma apply_empty : ∀ (A : Type) (x : string),
@empty A x = None.
Lemma update_eq : ∀ (A : Type) (m : partial_map A) x v,
(x ⊢> v ; m) x = Some v.
Theorem update_neq : ∀ (A : Type) (m : partial_map A) x1 x2 v,
x2 ≠ x1 →
(x2 ⊢> v ; m) x1 = m x1.
Lemma update_shadow : ∀ (A : Type) (m : partial_map A) x v1 v2,
(x ⊢> v2 ; x ⊢> v1 ; m) = (x ⊢> v2 ; m).
Theorem update_same : ∀ (A : Type) (m : partial_map A) x v,
m x = Some v →
(x ⊢> v ; m) = m.
Theorem update_permute : ∀ (A : Type) (m : partial_map A)
x1 x2 v1 v2,
x2 ≠ x1 →
(x1 ⊢> v1 ; x2 ⊢> v2 ; m) = (x2 ⊢> v2 ; x1 ⊢> v1 ; m).
@empty A x = None.
Lemma update_eq : ∀ (A : Type) (m : partial_map A) x v,
(x ⊢> v ; m) x = Some v.
Theorem update_neq : ∀ (A : Type) (m : partial_map A) x1 x2 v,
x2 ≠ x1 →
(x2 ⊢> v ; m) x1 = m x1.
Lemma update_shadow : ∀ (A : Type) (m : partial_map A) x v1 v2,
(x ⊢> v2 ; x ⊢> v1 ; m) = (x ⊢> v2 ; m).
Theorem update_same : ∀ (A : Type) (m : partial_map A) x v,
m x = Some v →
(x ⊢> v ; m) = m.
Theorem update_permute : ∀ (A : Type) (m : partial_map A)
x1 x2 v1 v2,
x2 ≠ x1 →
(x1 ⊢> v1 ; x2 ⊢> v2 ; m) = (x2 ⊢> v2 ; x1 ⊢> v1 ; m).
One last thing: For partial maps, it's convenient to introduce a notion of map inclusion, stating that all the entries in one map are also present in another:
Definition includedin {A : Type} (m m' : partial_map A) :=
∀ x v, m x = Some v → m' x = Some v.
∀ x v, m x = Some v → m' x = Some v.
We can then show that map update preserves map inclusion -- that is:
Lemma includedin_update : ∀ (A : Type) (m m' : partial_map A)
(x : string) (vx : A),
includedin m m' →
includedin (x ⊢> vx ; m) (x ⊢> vx ; m').
(x : string) (vx : A),
includedin m m' →
includedin (x ⊢> vx ; m) (x ⊢> vx ; m').
This property is quite useful for reasoning about languages with
variable binding -- e.g., the Simply Typed Lambda Calculus, which
we will see in Programming Language Foundations, where maps are
used to keep track of which program variables are defined in a
given scope.