Mercurial > cpdt > repo
comparison src/GeneralRec.v @ 357:b01c7b3122cc
New release
author | Adam Chlipala <adam@chlipala.net> |
---|---|
date | Fri, 28 Oct 2011 18:26:16 -0400 |
parents | 50e1d338728c |
children | d1276004eec9 |
comparison
equal
deleted
inserted
replaced
356:50e1d338728c | 357:b01c7b3122cc |
---|---|
96 (** %\vspace{-.15in}% [[ | 96 (** %\vspace{-.15in}% [[ |
97 Inductive Acc (A : Type) (R : A -> A -> Prop) (x : A) : Prop := | 97 Inductive Acc (A : Type) (R : A -> A -> Prop) (x : A) : Prop := |
98 Acc_intro : (forall y : A, R y x -> Acc R y) -> Acc R x | 98 Acc_intro : (forall y : A, R y x -> Acc R y) -> Acc R x |
99 ]] | 99 ]] |
100 | 100 |
101 In prose, an element [x] is accessible for a relation [R] if every element %``%#"#less than#"#%''% [x] according to [R] is also accessible. Since [Acc] is defined inductively, we know that any accessibility proof involves a finite chain of invocations, in a certain sense which we can make formal. Building on Chapter 5's examples, let us define a co-inductive relation that is closer to the usual informal notion of %``%#"#absence of infinite decreasing chains.#"#%''% *) | 101 In prose, an element [x] is accessible for a relation [R] if every element %``%#"#less than#"#%''% [x] according to [R] is also accessible. Since [Acc] is defined inductively, we know that any accessibility proof involves a finite chain of invocations, in a certain sense that we can make formal. Building on Chapter 5's examples, let us define a co-inductive relation that is closer to the usual informal notion of %``%#"#absence of infinite decreasing chains.#"#%''% *) |
102 | 102 |
103 CoInductive isChain A (R : A -> A -> Prop) : stream A -> Prop := | 103 CoInductive isChain A (R : A -> A -> Prop) : stream A -> Prop := |
104 | ChainCons : forall x y s, isChain R (Cons y s) | 104 | ChainCons : forall x y s, isChain R (Cons y s) |
105 -> R y x | 105 -> R y x |
106 -> isChain R (Cons x (Cons y s)). | 106 -> isChain R (Cons x (Cons y s)). |
135 (forall x : A, (forall y : A, R y x -> P y) -> P x) -> | 135 (forall x : A, (forall y : A, R y x -> P y) -> P x) -> |
136 forall x : A, P x | 136 forall x : A, P x |
137 ]] | 137 ]] |
138 | 138 |
139 A call to %\index{Gallina terms!Fix}%[Fix] must present a relation [R] and a proof of its well-foundedness. The next argument, [P], is the possibly dependent range type of the function we build; the domain [A] of [R] is the function's domain. The following argument has this type: | 139 A call to %\index{Gallina terms!Fix}%[Fix] must present a relation [R] and a proof of its well-foundedness. The next argument, [P], is the possibly dependent range type of the function we build; the domain [A] of [R] is the function's domain. The following argument has this type: |
140 | |
141 [[ | 140 [[ |
142 forall x : A, (forall y : A, R y x -> P y) -> P x | 141 forall x : A, (forall y : A, R y x -> P y) -> P x |
143 ]] | 142 ]] |
144 | 143 |
145 This is an encoding of the function body. The input [x] stands for the function argument, and the next input stands for the function we are defining. Recursive calls are encoded as calls to the second argument, whose type tells us it expects a value [y] and a proof that [y] is %``%#"#less than#"#%''% [x], according to [R]. In this way, we enforce the well-foundedness restriction on recursive calls. | 144 This is an encoding of the function body. The input [x] stands for the function argument, and the next input stands for the function we are defining. Recursive calls are encoded as calls to the second argument, whose type tells us it expects a value [y] and a proof that [y] is %``%#"#less than#"#%''% [x], according to [R]. In this way, we enforce the well-foundedness restriction on recursive calls. |
277 Some more recent Coq features provide more convenient syntax for defining recursive functions. Interested readers can consult the Coq manual about the commands %\index{Function}%[Function] and %\index{Program Fixpoint}%[Program Fixpoint]. *) | 276 Some more recent Coq features provide more convenient syntax for defining recursive functions. Interested readers can consult the Coq manual about the commands %\index{Function}%[Function] and %\index{Program Fixpoint}%[Program Fixpoint]. *) |
278 | 277 |
279 | 278 |
280 (** * A Non-Termination Monad Inspired by Domain Theory *) | 279 (** * A Non-Termination Monad Inspired by Domain Theory *) |
281 | 280 |
282 (** The key insights of %\index{domain theory}%domain theory%~\cite{WinskelDomains}% inspire the next approach to modeling non-termination. Domain theory is based on %\emph{%#<i>#information orders#</i>#%}% that relate values representing computatiion results, according to how much information these values convey. For instance, a simple domain might include values %``%#"#the program does not terminate#"#%''% and %``%#"#the program terminates with the answer 5.#"#%''% The former is considered to be an %\emph{%#<i>#approximation#</i>#%}% of the latter, while the latter is %\emph{%#<i>#not#</i>#%}% an approximation of %``%#"#the program terminates with the answer 6.#"#%''% The details of domain theory will not be important in what follows; we merely borrow the notion of an approximation ordering on computation results. | 281 (** The key insights of %\index{domain theory}%domain theory%~\cite{WinskelDomains}% inspire the next approach to modeling non-termination. Domain theory is based on %\emph{%#<i>#information orders#</i>#%}% that relate values representing computation results, according to how much information these values convey. For instance, a simple domain might include values %``%#"#the program does not terminate#"#%''% and %``%#"#the program terminates with the answer 5.#"#%''% The former is considered to be an %\emph{%#<i>#approximation#</i>#%}% of the latter, while the latter is %\emph{%#<i>#not#</i>#%}% an approximation of %``%#"#the program terminates with the answer 6.#"#%''% The details of domain theory will not be important in what follows; we merely borrow the notion of an approximation ordering on computation results. |
283 | 282 |
284 Consider this definition of a type of computations. *) | 283 Consider this definition of a type of computations. *) |
285 | 284 |
286 Section computation. | 285 Section computation. |
287 Variable A : Type. | 286 Variable A : Type. |
369 | [ H : _ = exist _ _ _ |- _ ] => rewrite H | 368 | [ H : _ = exist _ _ _ |- _ ] => rewrite H |
370 end. | 369 end. |
371 (* end hide *) | 370 (* end hide *) |
372 (** remove printing exists *) | 371 (** remove printing exists *) |
373 | 372 |
374 (** Now, as a simple first example of a computation, we can define [Bottom], which corresponds to an infinite loop. For any approximation level, it fails to terminate (returns [None]). Note the use of [abstract] to create a new opaque lemma for the proof found by the [run] tactic. In contrast to the previous section, opauqe proofs are fine here, since the proof components of computations do not influence evaluation behavior. *) | 373 (** Now, as a simple first example of a computation, we can define [Bottom], which corresponds to an infinite loop. For any approximation level, it fails to terminate (returns [None]). Note the use of [abstract] to create a new opaque lemma for the proof found by the [run] tactic. In contrast to the previous section, opaque proofs are fine here, since the proof components of computations do not influence evaluation behavior. *) |
375 | 374 |
376 Section Bottom. | 375 Section Bottom. |
377 Variable A : Type. | 376 Variable A : Type. |
378 | 377 |
379 Definition Bottom : computation A. | 378 Definition Bottom : computation A. |
407 Variable m1 : computation A. | 406 Variable m1 : computation A. |
408 Variable m2 : A -> computation B. | 407 Variable m2 : A -> computation B. |
409 | 408 |
410 Definition Bind : computation B. | 409 Definition Bind : computation B. |
411 exists (fun n => | 410 exists (fun n => |
412 let (f1, Hf1) := m1 in | 411 let (f1, _) := m1 in |
413 match f1 n with | 412 match f1 n with |
414 | None => None | 413 | None => None |
415 | Some v => | 414 | Some v => |
416 let (f2, Hf2) := m2 v in | 415 let (f2, _) := m2 v in |
417 f2 n | 416 f2 n |
418 end); abstract run. | 417 end); abstract run. |
419 Defined. | 418 Defined. |
420 | 419 |
421 Theorem run_Bind : forall (v1 : A) (v2 : B), | 420 Theorem run_Bind : forall (v1 : A) (v2 : B), |
445 Theorem right_identity : forall A (m : computation A), | 444 Theorem right_identity : forall A (m : computation A), |
446 meq (Bind m (@Return _)) m. | 445 meq (Bind m (@Return _)) m. |
447 run. | 446 run. |
448 Qed. | 447 Qed. |
449 | 448 |
450 Theorem associativity : forall A B C (m : computation A) (f : A -> computation B) (g : B -> computation C), | 449 Theorem associativity : forall A B C (m : computation A) |
450 (f : A -> computation B) (g : B -> computation C), | |
451 meq (Bind (Bind m f) g) (Bind m (fun x => Bind (f x) g)). | 451 meq (Bind (Bind m f) g) (Bind m (fun x => Bind (f x) g)). |
452 run. | 452 run. |
453 Qed. | 453 Qed. |
454 | 454 |
455 (** Now we come to the piece most directly inspired by domain theory. We want to support general recursive function definitions, but domain theory tells us that not all definitions are reasonable; some fail to be %\emph{%#<i>#continuous#</i>#%}% and thus represent unrealizable computations. To formalize an analogous notion of continuity for our non-termination monad, we write down the approximation relation on computation results that we have had in mind all along. *) | 455 (** Now we come to the piece most directly inspired by domain theory. We want to support general recursive function definitions, but domain theory tells us that not all definitions are reasonable; some fail to be %\emph{%#<i>#continuous#</i>#%}% and thus represent unrealizable computations. To formalize an analogous notion of continuity for our non-termination monad, we write down the approximation relation on computation results that we have had in mind all along. *) |
735 repeat (apply eval_frob; simpl; constructor). | 735 repeat (apply eval_frob; simpl; constructor). |
736 Qed. | 736 Qed. |
737 | 737 |
738 (** We need to apply constructors of [eval] explicitly, but the process is easy to automate completely for concrete input programs. | 738 (** We need to apply constructors of [eval] explicitly, but the process is easy to automate completely for concrete input programs. |
739 | 739 |
740 Now consider another very similar definition, this time of a Fibonacci number funtion. | 740 Now consider another very similar definition, this time of a Fibonacci number funtion. *) |
741 [[ | 741 |
742 Notation "x <- m1 ; m2" := | |
743 (TBind m1 (fun x => m2)) (right associativity, at level 70). | |
744 | |
745 (** %\vspace{-.15in}%[[ | |
742 CoFixpoint fib (n : nat) : thunk nat := | 746 CoFixpoint fib (n : nat) : thunk nat := |
743 match n with | 747 match n with |
744 | 0 => Answer 1 | 748 | 0 => Answer 1 |
745 | 1 => Answer 1 | 749 | 1 => Answer 1 |
746 | _ => TBind (fib (pred n)) (fun n1 => | 750 | _ => n1 <- fib (pred n); |
747 TBind (fib (pred (pred n))) (fun n2 => | 751 n2 <- fib (pred (pred n)); |
748 Answer (n1 + n2))) | 752 Answer (n1 + n2) |
749 end. | 753 end. |
750 ]] | 754 ]] |
751 | 755 |
752 Coq complains that the guardedness condition is violated. The two recursive calls are immediate arguments to [TBind], but [TBind] is not a constructor of [thunk]. Rather, it is a defined function. This example shows a very serious limitation of [thunk] for traditional functional programming: it is not, in general, possible to make recursive calls and then make further recursive calls, depending on the first call's result. The [fact] example succeeded because it was already tail recursive, meaning no further computation is needed after a recursive call. | 756 Coq complains that the guardedness condition is violated. The two recursive calls are immediate arguments to [TBind], but [TBind] is not a constructor of [thunk]. Rather, it is a defined function. This example shows a very serious limitation of [thunk] for traditional functional programming: it is not, in general, possible to make recursive calls and then make further recursive calls, depending on the first call's result. The [fact] example succeeded because it was already tail recursive, meaning no further computation is needed after a recursive call. |
753 | 757 |
879 >> | 883 >> |
880 | 884 |
881 The problem has to do with rules for inductive definitions that we still study in more detail in Chapter 12. Briefly, recall that the type of the constructor [Bnd] quantifies over a type [B]. To make [testCurriedAdd] work, we would need to instantiate [B] as [nat -> comp nat]. However, Coq enforces a %\emph{predicativity restriction}% that (roughly) no quantifier in an inductive or co-inductive type's definition may ever be instantiated with a term that contains the type being defined. Chapter 12 presents the exact mechanism by which this restriction is enforced, but for now our conclusion is that [comp] is fatally flawed as a way of encoding interesting higher-order functional programs that use general recursion. *) | 885 The problem has to do with rules for inductive definitions that we still study in more detail in Chapter 12. Briefly, recall that the type of the constructor [Bnd] quantifies over a type [B]. To make [testCurriedAdd] work, we would need to instantiate [B] as [nat -> comp nat]. However, Coq enforces a %\emph{predicativity restriction}% that (roughly) no quantifier in an inductive or co-inductive type's definition may ever be instantiated with a term that contains the type being defined. Chapter 12 presents the exact mechanism by which this restriction is enforced, but for now our conclusion is that [comp] is fatally flawed as a way of encoding interesting higher-order functional programs that use general recursion. *) |
882 | 886 |
883 | 887 |
884 (** * Comparing the Options *) | 888 (** * Comparing the Alternatives *) |
885 | 889 |
886 (** We have seen four different approaches to encoding general recursive definitions in Coq. Among them there is no clear champion that dominates the others in every important way. Instead, we close the chapter by comparing the techniques along a number of dimensions. Every technique allows recursive definitions with terminaton arguments that go beyond Coq's built-in termination checking, so we must turn to subtler points to highlight differences. | 890 (** We have seen four different approaches to encoding general recursive definitions in Coq. Among them there is no clear champion that dominates the others in every important way. Instead, we close the chapter by comparing the techniques along a number of dimensions. Every technique allows recursive definitions with terminaton arguments that go beyond Coq's built-in termination checking, so we must turn to subtler points to highlight differences. |
887 | 891 |
888 One useful property is automatic integration with normal Coq programming. That is, we would like the type of a function to be the same, whether or not that function is defined using an interesting recursion pattern. Only the first of the four techniques, well-founded recursion, meets this criterion. It is also the only one of the four to meet the related criterion that evaluation of function calls can take place entirely inside Coq's built-in computation machinery. The monad inspired by domain theory occupies some middle ground in this dimension, since generally standard computation is enough to evaluate a term once a high enough approximation level is provided. | 892 One useful property is automatic integration with normal Coq programming. That is, we would like the type of a function to be the same, whether or not that function is defined using an interesting recursion pattern. Only the first of the four techniques, well-founded recursion, meets this criterion. It is also the only one of the four to meet the related criterion that evaluation of function calls can take place entirely inside Coq's built-in computation machinery. The monad inspired by domain theory occupies some middle ground in this dimension, since generally standard computation is enough to evaluate a term once a high enough approximation level is provided. |
889 | 893 |
899 | 903 |
900 Definition curriedAdd' (n : nat) := Return (fun m : nat => Return (n + m)). | 904 Definition curriedAdd' (n : nat) := Return (fun m : nat => Return (n + m)). |
901 | 905 |
902 Definition testCurriedAdd := Bind (curriedAdd' 2) (fun f => f 3). | 906 Definition testCurriedAdd := Bind (curriedAdd' 2) (fun f => f 3). |
903 | 907 |
904 (** The same techniques also apply to more interesting higher-order functions like list map, and, as in all four techniques, we can mix regular and general recursion, preferring the former when possible to avoid proof obligations. *) | 908 (** The same techniques also apply to more interesting higher-order functions like list map, and, as in all four techniques, we can mix primitive and general recursion, preferring the former when possible to avoid proof obligations. *) |
905 | 909 |
906 Fixpoint map A B (f : A -> computation B) (ls : list A) : computation (list B) := | 910 Fixpoint map A B (f : A -> computation B) (ls : list A) : computation (list B) := |
907 match ls with | 911 match ls with |
908 | nil => Return nil | 912 | nil => Return nil |
909 | x :: ls' => Bind (f x) (fun x' => | 913 | x :: ls' => Bind (f x) (fun x' => |
917 exists 1; reflexivity. | 921 exists 1; reflexivity. |
918 Qed. | 922 Qed. |
919 | 923 |
920 (** One further disadvantage of [comp] is that we cannot prove an inversion lemma for executions of [Bind] without appealing to an %\emph{axiom}%, a logical complication that we discuss at more length in Chapter 12. The other three techniques allow proof of all the important theorems within the normal logic of Coq. | 924 (** One further disadvantage of [comp] is that we cannot prove an inversion lemma for executions of [Bind] without appealing to an %\emph{axiom}%, a logical complication that we discuss at more length in Chapter 12. The other three techniques allow proof of all the important theorems within the normal logic of Coq. |
921 | 925 |
922 Perhaps one theme of our comparison is that one must trade off between on one hand, functional programming expressiveness and compatibility with normal Coq types and computation; and, on the other hand, the level of proof obligations one is willing to handle at function definition time. *) | 926 Perhaps one theme of our comparison is that one must trade off between, on one hand, functional programming expressiveness and compatibility with normal Coq types and computation; and, on the other hand, the level of proof obligations one is willing to handle at function definition time. *) |