1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
|
Require Import Coq.Classes.Morphisms Coq.Program.Program Coq.Unicode.Utf8.
(* First, two ways to do quoting in the naive scenario without
holes/variables in the expression: *)
Module simple.
(* An example term language and evaluation: *)
Inductive Expr := Plus (a b: Expr) | Mult (a b: Expr) | Zero | One.
Fixpoint eval (e: Expr): nat :=
match e with
| Plus a b => eval a + eval b
| Mult a b => eval a * eval b
| Zero => 0
| One => 1
end.
(* First up is the simplest approach I can think of. *)
Module approach_A.
Class Quote (n: nat) := quote: Expr.
Arguments quote _ {Quote}.
Section instances.
Context n m `{Quote n} `{Quote m}.
Global Instance: Quote 0 := Zero.
Global Instance: Quote 1 := One.
Global Instance: Quote (n + m) := Plus (quote n) (quote m).
Global Instance: Quote (n * m) := Mult (quote n) (quote m).
End instances.
Ltac do_quote :=
match goal with
|- (?a = ?b) => change (eval (quote a) = eval (quote b))
end.
Lemma example: (1 + 0 + 1) * (1 + 1) = (1 + 1) + (1 + 1).
do_quote.
Admitted.
End approach_A.
(* This works, but there's something unsatisfying about this quotation, because
the actual Quote instances are not validated until we get to the [change] tactic,
which validates the quotation by requiring convertibility.
Next, we show an alternative implementation where the Quote instances
are all proved locally correct at their definition: *)
Module approach_B.
Class Quote (n: nat) := { quote: Expr; eval_quote: n = eval quote }.
Arguments quote _ {Quote}.
Arguments eval_quote _ {Quote}.
Section instances.
Context n m `{Quote n} `{Quote m}.
Global Program Instance: Quote 0 := { quote := Zero }.
Global Program Instance: Quote 1 := { quote := One }.
Global Instance: Quote (n + m).
Proof.
refine {| quote := Plus (quote n) (quote m) |}.
simpl. do 2 rewrite <- eval_quote. reflexivity.
Qed.
Global Instance: Quote (n * m).
Proof.
refine {| quote := Mult (quote n) (quote m) |}.
simpl. do 2 rewrite <- eval_quote. reflexivity.
Qed.
End instances.
Lemma do_quote {n m} `{Quote n} `{Quote m}: eval (quote n) = eval (quote m) → n = m.
Proof. intros. rewrite (eval_quote n), (eval_quote m). assumption. Qed.
Lemma example: (1 + 0 + 1) * (1 + 1) = (1 + 1) + (1 + 1).
apply do_quote.
Admitted.
End approach_B.
End simple.
(* So far so good, but the variable-less scenario isn't really interesting. We now rework approach B
to include quotation of holes/variables, including recognition of syntactically identical ones. *)
Module with_vars.
(* Some random utilities: *)
Lemma sum_assoc {A B C}: (A+B)+C → A+(B+C). intuition. Defined.
Lemma bla {A B C}: (A+B) → A+(B+C). intuition. Defined.
Lemma monkey {A B}: False + A → A + B. intuition. Defined.
Section obvious.
Class Obvious (T: Type) := obvious: T.
Context (A B C: Type).
Global Instance: Obvious (A → A) := id.
Global Instance: Obvious (False → A) := False_rect _.
Global Instance: Obvious (A → A + B) := inl.
Global Instance: Obvious (A → B + A) := inr.
Global Instance obvious_sum_src `{Obvious (A → C)} `{Obvious (B → C)}: Obvious (A+B → C). repeat intro. intuition. Defined.
Global Instance obvious_sum_dst_l `{Obvious (A → B)}: Obvious (A → B+C). repeat intro. intuition. Defined.
Global Instance obvious_sum_dst_r `{Obvious (A → B)}: Obvious (A → C+B). repeat intro. intuition. Defined.
End obvious.
(* Again our example term language, this time without plus/one (they're boring), but with Var
added: *)
Inductive Expr (V: Type) := Mult (a b: Expr V) | Zero | Var (v: V).
Arguments Var {V}.
Arguments Zero {V}.
Arguments Mult {V}.
(*
Require Import monads canonical_names.
Instance: MonadReturn Expr := fun _ => Var.
Instance expr_bind: MonadBind Expr := fun A B =>
fix F (m: Expr A) (f: A → Expr B): Expr B :=
match m with
| Zero => Zero
| Mult x y => Mult (F x f) (F y f)
| Var v => f v
end.
Section eqs.
Context `{e: Equiv A} `{Equivalence _ e}.
Global Instance expr_eq: Equiv (Expr A) :=
fix F (x y: Expr A) :=
match x, y with
| Var v, Var w => v = w
| Mult v w, Mult p q => F v p ∧ F w q
| Zero, Zero => True
| _, _ => False
end.
Instance: Reflexive expr_eq.
Proof. intro. induction x; simpl; intuition. Qed.
Instance: Symmetric expr_eq.
Proof. intro. induction x; destruct y; simpl in *; intuition. Qed.
Instance: Transitive expr_eq.
Admitted.
Global Instance expr_equivalence: Equivalence expr_eq.
End eqs.
Instance: ∀ `{Equiv A}, Proper ((=) ==> (=)) (ret Expr).
repeat intro.
assumption.
Qed.
Instance bind_proper: ∀ `{Equiv A} `{Equiv B},
Proper ((=) ==> pointwise_relation A (=) ==> (=)) (@expr_bind A B).
Proof.
intros A H B H0 x y E.
(*
induction x.
destruct y; intuition.
intros f g E'.
simpl.
red.
simpl.
split.
red in E.
simpl in E.
apply IHx2.
simpl in *.
unfold expr_bind.
simpl.
*)
Admitted.
Instance: Monad Expr.
*)
(* The expression type is parameterized over the set of variable indices. Hence, we diverge
from Claudio, who uses nat indices for variables, thereby introducing bounds problems and
dummy variables and other nastiness. *)
(* An expression is only meaningful in the context of a variable assignment: *)
Definition Value := nat.
Definition Vars V := V → Value.
Fixpoint eval {V} (vs: Vars V) (e: Expr V): Value :=
match e with
| Zero => 0
| Mult a b => eval vs a * eval vs b
| Var v => vs v
end.
#[global]
Instance eval_proper V: Proper (pointwise_relation _ eq ==> eq ==> eq) (@eval V).
Proof.
repeat intro. subst.
induction y0; simpl.
congruence.
reflexivity.
apply H.
Qed.
(* Some simple combinators for variable packs: *)
Definition novars: Vars False := False_rect _.
Definition singlevar (x: Value): Vars unit := fun _ => x.
Definition merge {A B} (a: Vars A) (b: Vars B): Vars (A+B) :=
fun i => match i with inl j => a j | inr j => b j end.
(* These last two combinators are the "constructors" of an implicitly defined subset of
Gallina terms (representing Claudio's "heaps") for which we implement syntactic
lookup with type classes: *)
Section Lookup.
(* Given a heap and value, Lookup instances give the value's index in the heap: *)
Class Lookup {A} (x: Value) (f: Vars A) := { lookup: A; lookup_correct: f lookup = x }.
Global Arguments lookup {A} _ _ {Lookup}.
Context (x: Value) {A B} (va: Vars A) (vb: Vars B).
(* If the heap is a merge of two heaps and we can find the value's index in the left heap,
we can access it by indexing the merged heap: *)
Global Instance lookup_left `{!Lookup x va}: Lookup x (merge va vb).
Proof.
refine {| lookup := inl (lookup x va) |}.
apply lookup_correct.
Defined.
(* And vice-versa: *)
Global Instance lookup_right `{!Lookup x vb} : Lookup x (merge va vb).
Proof.
refine {| lookup := inr (lookup x vb) |}.
apply lookup_correct.
Defined.
(* If the heap is just a singlevar, we can easily index it. *)
Global Program Instance: Lookup x (singlevar x) := { lookup := tt }.
(* Note that we don't have any fallback/default instances at this point. We /will/ introduce
such an instance for our Quote class later on, which will add a new variable to the heap
if another Quote instance that relies on Lookup into the "current" heap fails. *)
End Lookup.
(* One useful operation we need before we get to Quote relates to variables and expression
evaluation. As its name suggests, map_var maps an expression's variable indices. *)
Definition map_var {V W: Type} (f: V → W): Expr V → Expr W :=
fix F (e: Expr V): Expr W :=
match e with
| Mult a b => Mult (F a) (F b)
| Zero => Zero
| Var v => Var (f v)
end.
(* An obvious identity is: *)
Lemma eval_map_var {V W} (f: V → W) v e:
eval v (map_var f e) = eval (v ∘ f) e.
Proof.
induction e; simpl; try reflexivity.
rewrite IHe1, IHe2.
reflexivity.
Qed.
(* Finally, Quote itself: *)
Section Quote.
(* In Quote, the idea is that V, l, and n are all "input" variables, while V' and r are "output"
variables (in the sense that we will rely on unification to generate them. V and l represent
the "current heap", n represents the value we want to quote, and V' and r' represent the
heap of newly encountered variables during the quotation.
This explains the type of quote: it is an expression that refers either to variables from
the old heap, or to newly encountered variables.
eval_quote is the usual correctness property, which now merges the two heaps. *)
Class Quote {V} (l: Vars V) (n: Value) {V'} (r: Vars V') :=
{ quote: Expr (V + V')
; eval_quote: @eval (V+V') (merge l r) quote = n }.
Arguments quote {V l} _ {V' r Quote}.
(* Our first instance for Zero is easy. The "novars" in the result type reflects the fact that no new
variables are encountered. The correctness proof is easy enough for Program. *)
Global Program Instance quote_zero V (v: Vars V): Quote v 0 novars := { quote := Zero }.
(* The instance for multiplication is a bit more complex. The first line is just boring
variable declarations. The second line is important. "Quote x y z" must be read as
"quoting y with existing heap x generates new heap z", so the second line
basically just shuffles heaps around.
The third line has some ugly map_var's in it because the heap shuffling must be reflected
in the variable indices, but apart from that it's just constructing a Mult
term with quoted subterms. *)
Global Program Instance quote_mult V (v: Vars V) n V' (v': Vars V') m V'' (v'': Vars V'')
`{!Quote v n v'} `{!Quote (merge v v') m v''}: Quote v (n * m) (merge v' v'') :=
{ quote := Mult (map_var bla (quote n)) (map_var sum_assoc (quote m)) }.
Next Obligation. Proof with auto.
destruct Quote0, Quote1.
subst. simpl.
do 2 rewrite eval_map_var.
f_equal; apply eval_proper; auto; intro; intuition.
Qed.
(* Now follows the instance where we recognize values that are already in the heap. This
is expressed by the Lookup requirement, which will only be fulfilled if the Lookup instances
defined above can find the value in the heap. The novars in the [Quote v x novars] result
reflects that this quotation does not generate new variables. *)
Global Program Instance quote_old_var V (v: Vars V) x {i: Lookup x v}:
Quote v x novars | 8 := { quote := Var (inl (lookup x v)) }.
Next Obligation. Proof. apply lookup_correct. Qed.
(* Finally, the instance for new variables. We give this lower priority so that it is only
used if Lookup fails. *)
Global Program Instance quote_new_var V (v: Vars V) x: Quote v x (singlevar x) | 9
:= { quote := Var (inr tt) }.
End Quote.
(* Note: Explicitly using dynamically configured variable index sets instead of plain lists
not only removes the need for an awkward dummy value to cope with out-of-bounds
accesses, but also means that we can prove the correctness class fields in
Lookup/Quote without having to take the potential for out-of-bounds indexing into
account (which would be a nightmare). *)
(* When quoting something from scratch we will want to start with an empty heap.
To avoid having to mention this, we define quote' and eval_quote': *)
Definition quote': ∀ x {V'} {v: Vars V'} {d: Quote novars x v}, Expr _ := @quote _ _.
Definition eval_quote': ∀ x {V'} {v: Vars V'} {d: Quote novars x v},
eval (merge novars v) quote = x
:= @eval_quote _ _ .
Arguments quote' _ {V' v d}.
Arguments eval_quote' _ {V' v d}.
(* Time for some tests! *)
Goal ∀ x y (P: Value → Prop), P ((x * y) * (x * 0)).
intros.
rewrite <- (eval_quote' _).
(* turns the goal into
P (eval some_variable_pack_composed_from_combinators quote)
*)
simpl quote.
Admitted.
(* We can also inspect quotations more directly: *)
Section inspect.
Variables x y: Value.
(* Eval compute in quote' ((x * y) * (x * 0)). *)
(* = Mult (Mult (Var (inr (inl (inl ())))) (Var (inr (inl (inr ())))))
(Mult (Var (inr (inl (inl ())))) Zero)
: Expr (False + (() + () + (False + False))) *)
(* The second occurrence of (Var (inr (inl (inl ())))) means
the quoting has successfully noticed that it's the same
expression. *)
(* The two units in the generated variable index type reflect the
fact that the expression contains two variables. *)
(* I think adding some additional Quote instances might let us
get rid of the False's, but at the moment I see little reason to. *)
End inspect.
(* If we want to quote an equation between two expressions we should make
sure that the both sides refer to the same variable pack, and for that we write a
little utility function. It does the same kind of shuffling that the mult
Quote instance did. *)
Lemma quote_equality {V} {v: Vars V} {V'} {v': Vars V'} (l r: Value) `{!Quote novars l v} `{!Quote v r v'}:
let heap := (merge v v') in
eval heap (map_var monkey quote) = eval heap quote → l = r.
Proof with intuition.
destruct Quote0 as [lq []].
destruct Quote1 as [rq []].
intros heap H.
subst heap. simpl in H.
rewrite <- H, eval_map_var.
apply eval_proper... intro...
Qed.
Goal ∀ x y, x * y = y * x.
intros.
apply (quote_equality _ _).
simpl quote.
unfold map_var, monkey, sum_rect.
Admitted.
End with_vars.
|