Mercurial > cpdt > repo
comparison src/ProgLang.v @ 398:05efde66559d
Get it working in Coq 8.4beta1; use nice coqdoc notation for italics
author | Adam Chlipala <adam@chlipala.net> |
---|---|
date | Wed, 06 Jun 2012 11:25:13 -0400 |
parents | d5112c099fbf |
children | 73f8165a3c1d |
comparison
equal
deleted
inserted
replaced
397:d62ed7381a0b | 398:05efde66559d |
---|---|
80 (** Now we have some choices as to how we represent the syntax of programs. The two sections of the chapter explore two such choices, demonstrating the effect the choice has on proof complexity. *) | 80 (** Now we have some choices as to how we represent the syntax of programs. The two sections of the chapter explore two such choices, demonstrating the effect the choice has on proof complexity. *) |
81 | 81 |
82 | 82 |
83 (** * Dependent de Bruijn Indices *) | 83 (** * Dependent de Bruijn Indices *) |
84 | 84 |
85 (** The first encoding is one we met first in Chapter 9, the %\emph{%#<i>#dependent de Bruijn index#</i>#%}% encoding. We represent program syntax terms in a type familiy parametrized by a list of types, representing the %\emph{%#<i>#typing context#</i>#%}%, or information on which free variables are in scope and what their types are. Variables are represented in a way isomorphic to the natural numbers, where number 0 represents the first element in the context, number 1 the second element, and so on. Actually, instead of numbers, we use the [member] dependent type family from Chapter 9. *) | 85 (** The first encoding is one we met first in Chapter 9, the _dependent de Bruijn index_ encoding. We represent program syntax terms in a type familiy parametrized by a list of types, representing the _typing context_, or information on which free variables are in scope and what their types are. Variables are represented in a way isomorphic to the natural numbers, where number 0 represents the first element in the context, number 1 the second element, and so on. Actually, instead of numbers, we use the [member] dependent type family from Chapter 9. *) |
86 | 86 |
87 Module FirstOrder. | 87 Module FirstOrder. |
88 | 88 |
89 (** Here is the definition of the [term] type, including variables, constants, addition, function abstraction and application, and let binding of local variables. *) | 89 (** Here is the definition of the [term] type, including variables, constants, addition, function abstraction and application, and let binding of local variables. *) |
90 | 90 |
122 | App _ _ _ e1 e2 => fun s => (termDenote e1 s) (termDenote e2 s) | 122 | App _ _ _ e1 e2 => fun s => (termDenote e1 s) (termDenote e2 s) |
123 | 123 |
124 | Let _ _ _ e1 e2 => fun s => termDenote e2 (termDenote e1 s ::: s) | 124 | Let _ _ _ e1 e2 => fun s => termDenote e2 (termDenote e1 s ::: s) |
125 end. | 125 end. |
126 | 126 |
127 (** With this term representation, some program transformations are easy to implement and prove correct. Certainly we would be worried if this were not the the case for the %\emph{%#<i>#identity#</i>#%}% transformation, which takes a term apart and reassembles it. *) | 127 (** With this term representation, some program transformations are easy to implement and prove correct. Certainly we would be worried if this were not the the case for the _identity_ transformation, which takes a term apart and reassembles it. *) |
128 | 128 |
129 Fixpoint ident G t (e : term G t) : term G t := | 129 Fixpoint ident G t (e : term G t) : term G t := |
130 match e with | 130 match e with |
131 | Var _ _ x => Var x | 131 | Var _ _ x => Var x |
132 | 132 |
142 Theorem identSound : forall G t (e : term G t) s, | 142 Theorem identSound : forall G t (e : term G t) s, |
143 termDenote (ident e) s = termDenote e s. | 143 termDenote (ident e) s = termDenote e s. |
144 induction e; t. | 144 induction e; t. |
145 Qed. | 145 Qed. |
146 | 146 |
147 (** A slightly more ambitious transformation belongs to the family of %\emph{%#<i>#constant folding#</i>#%}% optimizations we have used as examples in other chapters. *) | 147 (** A slightly more ambitious transformation belongs to the family of _constant folding_ optimizations we have used as examples in other chapters. *) |
148 | |
149 Axiom admit : forall T, T. | |
148 | 150 |
149 Fixpoint cfold G t (e : term G t) : term G t := | 151 Fixpoint cfold G t (e : term G t) : term G t := |
150 match e with | 152 match e with |
151 | Plus G e1 e2 => | 153 | Plus G e1 e2 => |
152 let e1' := cfold e1 in | 154 let e1' := cfold e1 in |
153 let e2' := cfold e2 in | 155 let e2' := cfold e2 in |
154 match e1', e2' return _ with | 156 let maybeOpt := match e1' return _ with |
155 | Const _ n1, Const _ n2 => Const (n1 + n2) | 157 | Const _ n1 => |
156 | _, _ => Plus e1' e2' | 158 match e2' return _ with |
157 end | 159 | Const _ n2 => Some (Const (n1 + n2)) |
160 | _ => None | |
161 end | |
162 | _ => None | |
163 end in | |
164 match maybeOpt with | |
165 | None => Plus e1' e2' | |
166 | Some e' => e' | |
167 end | |
158 | 168 |
159 | Abs _ _ _ e1 => Abs (cfold e1) | 169 | Abs _ _ _ e1 => Abs (cfold e1) |
160 | App _ _ _ e1 e2 => App (cfold e1) (cfold e2) | 170 | App _ _ _ e1 e2 => App (cfold e1) (cfold e2) |
161 | 171 |
162 | Let _ _ _ e1 e2 => Let (cfold e1) (cfold e2) | 172 | Let _ _ _ e1 e2 => Let (cfold e1) (cfold e2) |
176 | Let _ _ _ _ _ => _ | 186 | Let _ _ _ _ _ => _ |
177 end] ] => dep_destruct E | 187 end] ] => dep_destruct E |
178 end; t). | 188 end; t). |
179 Qed. | 189 Qed. |
180 | 190 |
181 (** The transformations we have tried so far have been straightforward because they do not have interesting effects on the variable binding structure of terms. The dependent de Bruijn representation is called %\index{first-order syntax}\emph{%#<i>#first-order#</i>#%}% because it encodes variable identity explicitly; all such representations incur bookkeeping overheads in transformations that rearrange binding structure. | 191 (** The transformations we have tried so far have been straightforward because they do not have interesting effects on the variable binding structure of terms. The dependent de Bruijn representation is called %\index{first-order syntax}%_first-order_ because it encodes variable identity explicitly; all such representations incur bookkeeping overheads in transformations that rearrange binding structure. |
182 | 192 |
183 As an example of a tricky transformation, consider one that removes all uses of %``%#"#[let x = e1 in e2]#"#%''% by substituting [e1] for [x] in [e2]. We will implement the translation by pairing the %``%#"#compile-time#"#%''% typing environment with a %``%#"#run-time#"#%''% value environment or %\emph{%#<i>#substitution#</i>#%}%, mapping each variable to a value to be substituted for it. Such a substitute term may be placed within a program in a position with a larger typing environment than applied at the point where the substitute term was chosen. To support such context transplantation, we need %\emph{%#<i>#lifting#</i>#%}%, a standard de Bruijn indices operation. With dependent typing, lifting corresponds to weakening for typing judgments. | 193 As an example of a tricky transformation, consider one that removes all uses of %``%#"#[let x = e1 in e2]#"#%''% by substituting [e1] for [x] in [e2]. We will implement the translation by pairing the %``%#"#compile-time#"#%''% typing environment with a %``%#"#run-time#"#%''% value environment or _substitution_, mapping each variable to a value to be substituted for it. Such a substitute term may be placed within a program in a position with a larger typing environment than applied at the point where the substitute term was chosen. To support such context transplantation, we need _lifting_, a standard de Bruijn indices operation. With dependent typing, lifting corresponds to weakening for typing judgments. |
184 | 194 |
185 The fundamental goal of lifting is to add a new variable to a typing context, maintaining the validity of a term in the expanded context. To express the operation of adding a type to a context, we use a helper function [insertAt]. *) | 195 The fundamental goal of lifting is to add a new variable to a typing context, maintaining the validity of a term in the expanded context. To express the operation of adding a type to a context, we use a helper function [insertAt]. *) |
186 | 196 |
187 Fixpoint insertAt (t : type) (G : list type) (n : nat) {struct n} : list type := | 197 Fixpoint insertAt (t : type) (G : list type) (n : nat) {struct n} : list type := |
188 match n with | 198 match n with |
220 | App _ _ _ e1 e2 => App (lift' t' n e1) (lift' t' n e2) | 230 | App _ _ _ e1 e2 => App (lift' t' n e1) (lift' t' n e2) |
221 | 231 |
222 | Let _ _ _ e1 e2 => Let (lift' t' n e1) (lift' t' (S n) e2) | 232 | Let _ _ _ e1 e2 => Let (lift' t' n e1) (lift' t' (S n) e2) |
223 end. | 233 end. |
224 | 234 |
225 (** In the [Let] removal transformation, we only need to apply lifting to add a new variable at the %\emph{%#<i>#beginning#</i>#%}% of a typing context, so we package lifting into this final, simplified form. *) | 235 (** In the [Let] removal transformation, we only need to apply lifting to add a new variable at the _beginning_ of a typing context, so we package lifting into this final, simplified form. *) |
226 | 236 |
227 Definition lift G t' t (e : term G t) : term (t' :: G) t := | 237 Definition lift G t' t (e : term G t) : term (t' :: G) t := |
228 lift' t' O e. | 238 lift' t' O e. |
229 | 239 |
230 (** Finally, we can implement [Let] removal. The argument of type [hlist (term G') G] represents a substitution mapping each variable from context [G] into a term that is valid in context [G']. Note how the [Abs] case (1) extends via lifting the substitution [s] to hold in the broader context of the abstraction body [e1] and (2) maps the new first variable to itself. It is only the [Let] case that maps a variable to any substitute beside itself. *) | 240 (** Finally, we can implement [Let] removal. The argument of type [hlist (term G') G] represents a substitution mapping each variable from context [G] into a term that is valid in context [G']. Note how the [Abs] case (1) extends via lifting the substitution [s] to hold in the broader context of the abstraction body [e1] and (2) maps the new first variable to itself. It is only the [Let] case that maps a variable to any substitute beside itself. *) |
306 (** The [Let] removal optimization is a good case study of a simple transformation that may turn out to be much more work than expected, based on representation choices. In the second part of this chapter, we consider an alternate choice that produces a more pleasant experience. *) | 316 (** The [Let] removal optimization is a good case study of a simple transformation that may turn out to be much more work than expected, based on representation choices. In the second part of this chapter, we consider an alternate choice that produces a more pleasant experience. *) |
307 | 317 |
308 | 318 |
309 (** * Parametric Higher-Order Abstract Syntax *) | 319 (** * Parametric Higher-Order Abstract Syntax *) |
310 | 320 |
311 (** In contrast to first-order encodings, %\index{higher-order syntax}\emph{%#<i>#higher-order#</i>#%}% encodings avoid explicit modeling of variable identity. Instead, the binding constructs of an %\index{object language}\emph{%#<i>#object language#</i>#%}% (the language being formalized) can be represented using the binding constructs of the %\index{meta language}\emph{%#<i>#meta language#</i>#%}% (the language in which the formalization is done). The best known higher-order encoding is called %\index{higher-order abstract syntax}\index{HOAS}\emph{%#<i>#higher-order abstract syntax (HOAS)#</i>#%}~\cite{HOAS}%, and we can start by attempting to apply it directly in Coq. *) | 321 (** In contrast to first-order encodings, %\index{higher-order syntax}%_higher-order_ encodings avoid explicit modeling of variable identity. Instead, the binding constructs of an %\index{object language}%_object language_ (the language being formalized) can be represented using the binding constructs of the %\index{meta language}%_meta language_ (the language in which the formalization is done). The best known higher-order encoding is called %\index{higher-order abstract syntax}\index{HOAS}%_higher-order abstract syntax (HOAS)_ %\cite{HOAS}%, and we can start by attempting to apply it directly in Coq. *) |
312 | 322 |
313 Module HigherOrder. | 323 Module HigherOrder. |
314 | 324 |
315 (** With HOAS, each object language binding construct is represented with a %\emph{%#<i>#function#</i>#%}% of the meta language. Here is what we get if we apply that idea within an inductive definition of term syntax. *) | 325 (** With HOAS, each object language binding construct is represented with a _function_ of the meta language. Here is what we get if we apply that idea within an inductive definition of term syntax. *) |
316 | 326 |
317 (** %\vspace{-.15in}%[[ | 327 (** %\vspace{-.15in}%[[ |
318 Inductive term : type -> Type := | 328 Inductive term : type -> Type := |
319 | Const : nat -> term Nat | 329 | Const : nat -> term Nat |
320 | Plus : term Nat -> term Nat -> term Nat | 330 | Plus : term Nat -> term Nat -> term Nat |
325 | Let : forall t1 t2, term t1 -> (term t1 -> term t2) -> term t2. | 335 | Let : forall t1 t2, term t1 -> (term t1 -> term t2) -> term t2. |
326 ]] | 336 ]] |
327 | 337 |
328 However, Coq rejects this definition for failing to meet the %\index{strict positivity restriction}%strict positivity restriction. For instance, the constructor [Abs] takes an argument that is a function over the same type family [term] that we are defining. Inductive definitions of this kind can be used to write non-terminating Gallina programs, which breaks the consistency of Coq's logic. | 338 However, Coq rejects this definition for failing to meet the %\index{strict positivity restriction}%strict positivity restriction. For instance, the constructor [Abs] takes an argument that is a function over the same type family [term] that we are defining. Inductive definitions of this kind can be used to write non-terminating Gallina programs, which breaks the consistency of Coq's logic. |
329 | 339 |
330 An alternate higher-order encoding is %\index{parametric higher-order abstract syntax}\index{PHOAS}\emph{%#<i>#parametric HOAS#</i>#%}%, as introduced by Washburn and Weirich%~\cite{BGB}% for Haskell and tweaked by me%~\cite{PhoasICFP08}% for use in Coq. Here the idea is to parametrize the syntax type by a type family standing for a %\emph{%#<i>#representation of variables#</i>#%}%. *) | 340 An alternate higher-order encoding is %\index{parametric higher-order abstract syntax}\index{PHOAS}%_parametric HOAS_, as introduced by Washburn and Weirich%~\cite{BGB}% for Haskell and tweaked by me%~\cite{PhoasICFP08}% for use in Coq. Here the idea is to parametrize the syntax type by a type family standing for a _representation of variables_. *) |
331 | 341 |
332 Section var. | 342 Section var. |
333 Variable var : type -> Type. | 343 Variable var : type -> Type. |
334 | 344 |
335 Inductive term : type -> Type := | 345 Inductive term : type -> Type := |
346 | 356 |
347 Implicit Arguments Var [var t]. | 357 Implicit Arguments Var [var t]. |
348 Implicit Arguments Const [var]. | 358 Implicit Arguments Const [var]. |
349 Implicit Arguments Abs [var dom ran]. | 359 Implicit Arguments Abs [var dom ran]. |
350 | 360 |
351 (** Coq accepts this definition because our embedded functions now merely take %\emph{%#<i>#variables#</i>#%}% as arguments, instead of arbitrary terms. One might wonder whether there is an easy loophole to exploit here, instantiating the parameter [var] as [term] itself. However, to do that, we would need to choose a variable representation for this nested mention of [term], and so on through an infinite descent into [term] arguments. | 361 (** Coq accepts this definition because our embedded functions now merely take _variables_ as arguments, instead of arbitrary terms. One might wonder whether there is an easy loophole to exploit here, instantiating the parameter [var] as [term] itself. However, to do that, we would need to choose a variable representation for this nested mention of [term], and so on through an infinite descent into [term] arguments. |
352 | 362 |
353 We write the final type of a closed term using polymorphic quantification over all possible choices of [var] type family. *) | 363 We write the final type of a closed term using polymorphic quantification over all possible choices of [var] type family. *) |
354 | 364 |
355 Definition Term t := forall var, term var t. | 365 Definition Term t := forall var, term var t. |
356 | 366 |
357 (** Here are the new representations of the example terms from the last section. Note how each is written as a function over a [var] choice, such that the specific choice has no impact on the %\emph{%#<i>#structure#</i>#%}% of the term. *) | 367 (** Here are the new representations of the example terms from the last section. Note how each is written as a function over a [var] choice, such that the specific choice has no impact on the _structure_ of the term. *) |
358 | 368 |
359 Example add : Term (Func Nat (Func Nat Nat)) := fun var => | 369 Example add : Term (Func Nat (Func Nat Nat)) := fun var => |
360 Abs (fun x => Abs (fun y => Plus (Var x) (Var y))). | 370 Abs (fun x => Abs (fun y => Plus (Var x) (Var y))). |
361 | 371 |
362 Example three_the_hard_way : Term Nat := fun var => | 372 Example three_the_hard_way : Term Nat := fun var => |
363 App (App (add var) (Const 1)) (Const 2). | 373 App (App (add var) (Const 1)) (Const 2). |
364 | 374 |
365 (** The argument [var] does not even appear in the function body for [add]. How can that be? By giving our terms expressive types, we allow Coq to infer many arguments for us. In fact, we do not even need to name the [var] argument! Even though these formal parameters appear as underscores, they %\emph{%#<i>#are#</i>#%}% mentioned in the function bodies that type inference calculates. *) | 375 (** The argument [var] does not even appear in the function body for [add]. How can that be? By giving our terms expressive types, we allow Coq to infer many arguments for us. In fact, we do not even need to name the [var] argument! Even though these formal parameters appear as underscores, they _are_ mentioned in the function bodies that type inference calculates. *) |
366 | 376 |
367 Example add' : Term (Func Nat (Func Nat Nat)) := fun _ => | 377 Example add' : Term (Func Nat (Func Nat Nat)) := fun _ => |
368 Abs (fun x => Abs (fun y => Plus (Var x) (Var y))). | 378 Abs (fun x => Abs (fun y => Plus (Var x) (Var y))). |
369 | 379 |
370 Example three_the_hard_way' : Term Nat := fun _ => | 380 Example three_the_hard_way' : Term Nat := fun _ => |
371 App (App (add' _) (Const 1)) (Const 2). | 381 App (App (add' _) (Const 1)) (Const 2). |
372 | 382 |
373 | 383 |
374 (** ** Functional Programming with PHOAS *) | 384 (** ** Functional Programming with PHOAS *) |
375 | 385 |
376 (** It may not be at all obvious that the PHOAS representation admits the crucial computable operations. The key to effective deconstruction of PHOAS terms is one principle: treat the [var] parameter as an unconstrained choice of %\emph{%#<i>#which data should be annotated on each variable#</i>#%}%. We will begin with a simple example, that of counting how many variable nodes appear in a PHOAS term. This operation requires no data annotated on variables, so we simply annotate variables with [unit] values. Note that, when we go under binders in the cases for [Abs] and [Let], we must provide the data value to annotate on the new variable we pass beneath. For our current choice of [unit] data, we always pass [tt]. *) | 386 (** It may not be at all obvious that the PHOAS representation admits the crucial computable operations. The key to effective deconstruction of PHOAS terms is one principle: treat the [var] parameter as an unconstrained choice of _which data should be annotated on each variable_. We will begin with a simple example, that of counting how many variable nodes appear in a PHOAS term. This operation requires no data annotated on variables, so we simply annotate variables with [unit] values. Note that, when we go under binders in the cases for [Abs] and [Let], we must provide the data value to annotate on the new variable we pass beneath. For our current choice of [unit] data, we always pass [tt]. *) |
377 | 387 |
378 Fixpoint countVars t (e : term (fun _ => unit) t) : nat := | 388 Fixpoint countVars t (e : term (fun _ => unit) t) : nat := |
379 match e with | 389 match e with |
380 | Var _ _ => 1 | 390 | Var _ _ => 1 |
381 | 391 |
425 (** %\vspace{-.15in}%[[ | 435 (** %\vspace{-.15in}%[[ |
426 = "(((fun x => (fun x' => (x + x'))) N) N)" | 436 = "(((fun x => (fun x' => (x + x'))) N) N)" |
427 ]] | 437 ]] |
428 *) | 438 *) |
429 | 439 |
430 (** However, it is not necessary to convert to first-order form to support many common operations on terms. For instance, we can implement substitution of one term in another. The key insight here is to %\emph{%#<i>#tag variables with terms#</i>#%}%, so that, on encountering a variable, we can simply replace it by the term in its tag. We will call this function initially on a term with exactly one free variable, tagged with the appropriate substitute. During recursion, new variables are added, but they are only tagged with their own term equivalents. Note that this function [squash] is parametrized over a specific [var] choice. *) | 440 (** However, it is not necessary to convert to first-order form to support many common operations on terms. For instance, we can implement substitution of one term in another. The key insight here is to _tag variables with terms_, so that, on encountering a variable, we can simply replace it by the term in its tag. We will call this function initially on a term with exactly one free variable, tagged with the appropriate substitute. During recursion, new variables are added, but they are only tagged with their own term equivalents. Note that this function [squash] is parametrized over a specific [var] choice. *) |
431 | 441 |
432 Fixpoint squash var t (e : term (term var) t) : term var t := | 442 Fixpoint squash var t (e : term (term var) t) : term var t := |
433 match e with | 443 match e with |
434 | Var _ e1 => e1 | 444 | Var _ e1 => e1 |
435 | 445 |
461 (fun x : var Nat => | 471 (fun x : var Nat => |
462 Abs (fun y : var Nat => Plus (Var x) (Var y)))) | 472 Abs (fun y : var Nat => Plus (Var x) (Var y)))) |
463 (Const 1)) (Const 2)) (Const 3) | 473 (Const 1)) (Const 2)) (Const 3) |
464 ]] | 474 ]] |
465 | 475 |
466 One further development, which may seem surprising at first, is that we can also implement a usual term denotation function, when we %\emph{%#<i>#tag variables with their denotations#</i>#%}%. *) | 476 One further development, which may seem surprising at first, is that we can also implement a usual term denotation function, when we _tag variables with their denotations_. *) |
467 | 477 |
468 Fixpoint termDenote t (e : term typeDenote t) : typeDenote t := | 478 Fixpoint termDenote t (e : term typeDenote t) : typeDenote t := |
469 match e with | 479 match e with |
470 | Var _ v => v | 480 | Var _ v => v |
471 | 481 |
692 Have we really avoided first-order reasoning about the output terms of translations? The answer depends on some subtle issues, which deserve a subsection of their own. *) | 702 Have we really avoided first-order reasoning about the output terms of translations? The answer depends on some subtle issues, which deserve a subsection of their own. *) |
693 | 703 |
694 | 704 |
695 (** ** Establishing Term Well-Formedness *) | 705 (** ** Establishing Term Well-Formedness *) |
696 | 706 |
697 (** Can there be values of type [Term t] that are not well-formed according to [Wf]? We expect that Gallina satisfies key %\index{parametricity}\emph{%#<i>#parametricity#</i>#%}~\cite{parametricity}% properties, which indicate how polymorphic types may only be inhabited by specific values. We omit details of parametricity theorems here, but [forall t (E : Term t), Wf E] follows the flavor of such theorems. One option would be to assert that fact as an axiom, %``%#"#proving#"#%''% that any output of any of our translations is well-formed. We could even prove the soundness of the theorem on paper meta-theoretically, say by considering some particular model of CIC. | 707 (** Can there be values of type [Term t] that are not well-formed according to [Wf]? We expect that Gallina satisfies key %\index{parametricity}%_parametricity_ %\cite{parametricity}% properties, which indicate how polymorphic types may only be inhabited by specific values. We omit details of parametricity theorems here, but [forall t (E : Term t), Wf E] follows the flavor of such theorems. One option would be to assert that fact as an axiom, %``%#"#proving#"#%''% that any output of any of our translations is well-formed. We could even prove the soundness of the theorem on paper meta-theoretically, say by considering some particular model of CIC. |
698 | 708 |
699 To be more cautious, we could prove [Wf] for every term that interests us, threading such proofs through all transformations. Here is an example exercise of that kind, for [Unlet]. | 709 To be more cautious, we could prove [Wf] for every term that interests us, threading such proofs through all transformations. Here is an example exercise of that kind, for [Unlet]. |
700 | 710 |
701 First, we prove that [wf] is %\emph{%#<i>#monotone#</i>#%}%, in that a given instance continues to hold as we add new variable pairs to the variable isomorphism. *) | 711 First, we prove that [wf] is _monotone_, in that a given instance continues to hold as we add new variable pairs to the variable isomorphism. *) |
702 | 712 |
703 Hint Constructors wf. | 713 Hint Constructors wf. |
704 Hint Extern 1 (In _ _) => simpl; tauto. | 714 Hint Extern 1 (In _ _) => simpl; tauto. |
705 Hint Extern 1 (Forall _ _) => eapply Forall_weaken; [ eassumption | simpl ]. | 715 Hint Extern 1 (Forall _ _) => eapply Forall_weaken; [ eassumption | simpl ]. |
706 | 716 |