(** _Note_: One might want to eliminate the use of [pattern] in the proof script by replacing the corresponding line with the following:  [rewrite sunf_eq at 1; simpl.]  However, doing so will result in an invalid proof (rejected at [Qed]-time).  The reason is that the proof term created by [rewrite] ... [at 1] involves some lemmas from the [Setoid] library.  Like most lemmas, these are opaque, so guardedness checking cannot inspect their proofs and thus fails.  This is a great example of two of the previously mentioned problems with the [cofix] approach, namely lack of compositionality and poor interaction with standard tactics. *)(* *)
(** *** A proof using our [pcofix]  We will now do the same proof, but using the [paco] library.  We first simply show the new (but equivalent) definition of stream equality and the new proof of the example, and then explain what is going on behind the scenes.  In both, we use [paco] constructs with suffix "2" because we are dealing with predicates of arity 2 here.  _Note_: [Paco] supports predicates of arity up to 8.  Also, it supports up to three mutually coinductive predicates (see the last example of this tutorial).  In either case, extending this is just a matter of copy and paste.  We also have to prove monotonicity of the generating function [seq_gen], which can be discharged by the tactic [pmonauto], and register it in the Hint databse [paco].  _Remark_: Unlike [CoInductive], [paco] does not care whether the generating function is given in a _strictly positive_ syntactic form; all that matters is that the function is monotone. More specifically, [paco2 f] is well defined for an arbitrary generating function [f] regardless of whether it is monotone or not. However, in order to ensure that [paco2 f bot2] is the greatest fixed point of [f], we need monotonicity of [f]. *)
Hint Unfold seq'.

(* Specifications: *)
Lemma seq_gen_mon: monotone2 seq_gen. Proof. pmonauto. Qed.
Hint Resolve seq_gen_mon : paco.
Theorem example' : forall n, seq' (enumerate n) (cons n (map S (enumerate n))).
(** Observe that the proof script is essentially the same as before (this is no accident).  The main change is the use of [pcofix] instead of [cofix], which allows us to avoid any syntactic guardedness checking at [Qed]-time.  As a minor benefit of that, we are able to use [rewrite] ... [at 1], which we could not do before.  *)
(** **** How it works:  To understand [seq'] and the proof of [example'], we have to explain some background.  Given a monotone function [f], we call [paco2 f] the _parameterized_ greatest fixed point of [f].  For a relation [r], [paco2 f r] is defined as the greatest fixed point of the function [fun x => f (x \2/ r)].  Clearly, [paco2 f bot2] (where [bot2] is the empty relation) is just the ordinary greatest fixed point of [f].  Let us look at our example domain to understand better.  A proof of [paco2 seq_gen r s1 s2] can be seen as a proof of [r <2= seq -> seq s1 s2], where the use of the premise is guarded _semantically_, rather than syntactically.  More precisely, [paco2 seq_gen r] relates two streams iff their heads are equal and their tails are either related by [r] or again related by [paco2 seq_gen r].  Compare this to [seq], the ordinary greatest fixed point of [seq_gen], which relates two streams iff their heads are equal and their tails are again related by [seq].  This should also make it clear why our definition of [seq'] is equivalent to [seq].  To fold and unfold parameterized fixed points, we provide two tactics, where [upaco2 f r := paco2 f r \2/ r]:  - [pfold] : when the conclusion is [paco2 f r] for some [f] and [r], [pfold] converts it to [f (upaco2 f r)] - [punfold H] : when the hypothesis [H] is [paco2 f r] for some [f] and [r], [punfold H] converts it to [f (upaco2 f r)]  Other useful lemmas are:  - [paco2_mon f : monotone2 (paco2 f)] - [paco2_mult f : forall r, paco2 f (paco2 f r) <2= paco2 f r] - [paco2_mult_strong f : forall r, paco2 f (upaco2 f r) <2= paco2 f r]   We will see an example involving [paco2_mult] in a moment.  But first let us have another look at the proof scripts of [example] and [example'].  In the former, after calling [cofix], the proof state is as follows:  [CIH : forall n : nat, seq (enumerate n) (cons n (map S (enumerate n)))] #<br># [============================] #<br># [forall n : nat, seq (enumerate n) (cons n (map S (enumerate n)))] #<br>#  In this state, the hypothesis precisely matches the conclusion, and there is nothing preventing one from simply concluding the goal directly.  Of course, if one were to do that, one's "proof" would be subsequently rejected by [Qed] for failing to obey syntactic guardedness.  Calling [pcofix] results in a similar state, except that the added hypothesis [CIH] now governs a fresh relation variable [r], which represents the current coinduction hypothesis relating [enumerate n] and [cons n (map S (enumerate n))] for all [n].  The new goal then says that, in proving the two streams equal, we can use [r] coinductively, but only in a semantically guarded position, i.e. after unfolding [paco2 seq_gen r].  In particular, one _cannot_ apply [CIH] immediately to "solve" the goal:  [r : stream -> stream -> Prop] #<br># [CIH : forall n : nat, r (enumerate n) (cons n (map S (enumerate n)))] #<br># [============================] #<br># [forall n : nat, paco2 seq_gen r (enumerate n) (cons n (map S (enumerate n)))] #<br>#  The remaining differences between the proof scripts of [example] and [example'] are as follows.  First, let us examine [example]. After applying [seq_fold] and unfolding [enumerate n] in the proof of [example], we have the following goal:  [CIH : forall n : nat, seq (enumerate n) (cons n (map S (enumerate n)))] #<br># [n : nat] #<br># [============================] #<br># [seq_gen seq (cons n (enumerate (S n))) (cons n (map S (enumerate n)))] #<br>#  By the definition of [seq_gen] and unfolding [map S (enumerate n)], this reduces to showing  [CIH : forall n : nat, seq (enumerate n) (cons n (map S (enumerate n)))] #<br># [n : nat] #<br># [============================] #<br># [seq (enumerate (S n)) (cons (S n) (map S (enumerate (S n))))] #<br>#  which follows directly by applying the coinduction hypothesis [CIH].  In the case of [example'], the proof is slightly different. First, we use the tactic [pfold] rather than [apply seq_fold], simply because we now reason about [seq'] rather than [seq]. After applying [pfold] and unfolding [enumerate n], we have:  [r : stream -> stream -> Prop] #<br># [CIH : forall n : nat, r (enumerate n) (cons n (map S (enumerate n)))] #<br># [n : nat] #<br># [============================] #<br># [seq_gen (paco2 seq_gen r \2/ r) (cons n (enumerate (S n))) (cons n (map S (enumerate n)))] #<br>#  As you see, the relation [r] appears in the argument of [seq_gen]. Thus by definition of [seq_gen] and unfolding [map S (enumerate n)], we need to show [enumerate (S n)] and [cons (S n) (map S (enumerate (S n)))] are related by either [paco2 seq_gen r] or [r]:  [r : stream -> stream -> Prop] #<br># [CIH : forall n : nat, r (enumerate n) (cons n (map S (enumerate n)))] #<br># [n : nat] #<br># [============================] #<br># [paco2 seq_gen r (enumerate (S n)) (cons (S n) (map S (enumerate (S n))))] #<br># [ \/ r (enumerate (S n)) (cons (S n) (map S (enumerate (S n))))] #<br>#  As the coinduction hypothesis gives us that they are related by [r], we first have to select the [right] disjunct, before we apply [CIH].  Summing up, the two proofs are very similar, but in the one using [paco], the guardedness of the coinduction is guaranteed at every step by the way the proof is constructed, rather than by a syntactic check of the whole proof term after the fact. *)(* *)
(** *** Another example  Before moving on to the second part, we briefly demonstrate the use of the tactics [punfold] and [pclearbot]. *)(* *)
(** Here is a proof for [seq]. *)
Theorem seq_cons : forall n1 n2 s1 s2 (SEQ : seq (cons n1 s1) (cons n2 s2)),
n1 = n2 /\ seq s1 s2.