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. *)