[Agda] Tentative guidelines for working with coinductive types
Noam Zeilberger
noam+agda at cs.cmu.edu
Wed Jan 14 21:36:17 CET 2009
This is a nice design pattern. I was having trouble getting Agda to
accept some proofs about an equality relation ≡ for a little
coinductively defined language, and following these principles almost
all of the proofs went through easily. The remaining problem seems to
be the weakness of Agda's definitional equality. For example, I
define a cyclic term Ω in the syntax of the language, and then want to
prove that Ω ≡ Ω. Well, I can prove reflexivity in general by
recursion on terms, but I'm having trouble proving Ω ≡ Ω directly.
(If this doesn't make any sense, I can try to come up with a small
example.)
My one thought about the proposed changes to Agda is that, while
they do make good sense, it might end up being easier and less
bug-prone to adopt the more general, destructor-based view of
coinductive types.
Noam
On Tue, Jan 13, 2009 at 03:26:48PM +0000, Nils Anders Danielsson wrote:
> Hi,
>
> I think I have found a reasonably nice way to work with coinductive
> types in Agda. The method does not address productivity, which is a
> separate problem, but only evaluation, equality and similar things. It
> does seem to solve the problem with subject reduction.
>
> This method works today with the current implementation of codata in
> Agda. In this message I do not consider the wider issue of whether we
> should switch to a destructor-based view of coinductive types, but I
> note that the method is inspired by Anton Setzer's ideas about
> destructor-based approaches, as presented in messages to this list.
>
> Subject reduction
> -----------------
>
> As observed by Anton the problem with subject reduction does not occur
> if a destructor-based view of coinductive types is used. Thierry
> Coquand observed in a privately circulated note that when the
> destructor-based view is used there is only one evaluation mechanism;
> there is no separate mechanism to "delay" an expression.
>
> In the case of Agda it is the unfortunate combination of a delay
> mechanism (~) and dependent pattern matching which causes subject
> reduction to fail; codata per se does not have anything to do with it.
> (There may be other reasons for why subject reduction fails, I have not
> proved that Agda is always well-behaved; I only consider problems like
> the one raised by Nicolas Oury last year.)
>
> Consider the following code:
>
> data D : Set where
> d : D
>
> e : D
> e ~ d
>
> Here e is delayed, so Agda's definitional equality does not treat e and
> d as being equal. One can force delayed expressions by using pattern
> matching:
>
> force : D → D
> force d = d
>
> This appears to be unproblematic, but by using /dependent/ pattern
> matching one can prove that an unforced expression coincides with the
> forced one, because dependent pattern matching makes Agda "remember"
> the result of the pattern match, and hence also the result of forcing
> the delayed expression:
>
> force-eq : (x : D) → x ≡ force x
> force-eq d = ≡-refl
>
> p : e ≡ d
> p = force-eq e
>
> Now we have a closed proof of e ≡ d, which means that p evaluates to
> ≡-refl. Unfortunately the term
>
> q : e ≡ d
> q = ≡-refl
>
> does not type check, though, so we have lost subject reduction.
>
> To summarise: This particular problem with subject reduction can be
> avoided by never using dependent pattern matching to force delayed
> expressions.
>
> Coinductive types
> -----------------
>
> Let us now move on to coinductive types. Inspired by Anton's ideas
> about destructor-based approaches to coinductive types I suggest that
> there should only be a single "codata" type (plus variants for Set1,
> Set2 etc.):
>
> codata ∞ (T : Set) : Set where
> ♯_ : T → ∞ T
>
> This type comes with a destructor, which is the only function which is
> allowed to pattern match on ♯_:
>
> ♭ : ∀ {T} → ∞ T → T
> ♭ (♯ x) = x
>
> Coinductive types are then defined using /data/, but with ∞ annotating
> coinductive arguments (this was suggested by Thorsten Altenkirch):
>
> data Colist (A : Set) : Set where
> [] : Colist A
> _∷_ : A → ∞ (Colist A) → Colist A
>
> This makes it easy to choose which arguments should be inductive and
> which should be coinductive, so we can for instance define stream
> processors in a nice way:
>
> data Stream (A : Set) : Set where
> _∷_ : A → ∞ (Stream A) → Stream A
>
> data SP (A B : Set) : Set where
> get : (A → SP A B) → SP A B
> put : B → ∞ (SP A B) → SP A B
>
> Note also that the use of data plus ∞ corresponds quite closely to
> Anton's explanation of codata in terms of mutual coalg and data. The
> data part is put on the outside, though, so the destructor ♭ is
> applied in the (co)recursive calls instead of before pattern matching
> can take place, which means that we can avoid Anton's use of with:
>
> map : ∀ {A B} → (A → B) → Colist A → Colist B
> map f [] = []
> map f (x ∷ xs) = f x ∷ rec
> where rec ~ ♯ map f (♭ xs)
>
> eat : {A B : Set} → SP A B → Stream A → Stream B
> eat (get f) (a ∷ as) = eat (f a) (♭ as)
> eat (put b sp) as = b ∷ rec
> where rec ~ ♯ eat (♭ sp) as
>
> Note above that ~ is only used right before ♯_. This ensures that the
> delayed expression can always be forced using ♭. If ~ is never used
> anywhere else this also ensures that we do not get the subject
> reduction problem discussed above, because dependent pattern matching
> on ♯_ is not allowed.
>
> Proofs
> ------
>
> Let us now see how one can state and prove properties about
> coinductive values. Indexing on coinductive values seems to be fine,
> but just as it is sometimes a bad idea to use functions in a
> constructor's index expressions, it can also be a bad idea to use ♯_.
> I suggest using destructors instead, as in the following definition of
> colist equality:
>
> data _≈_ {A} : (xs ys : Colist A) → Set where
> [] : [] ≈ []
> _∷_ : ∀ {x y xs ys} → x ≡ y → ∞ (♭ xs ≈ ♭ ys) → x
> ∷ xs ≈ y ∷ ys
>
> Because the destructors are applied exactly where they are needed it
> is often straight-forward to prove things:
>
> map-cong : ∀ {A B} (f : A -> B) {xs ys : Colist A} ->
> xs ≈ ys -> map f xs ≈ map f ys
> map-cong f [] = []
> map-cong f (x≡ ∷ xs≈) = ≡-cong f x≡ ∷ rec
> where rec ~ ♯ map-cong f (♭ xs≈)
>
> Equality
> --------
>
> When comparing this approach to coinduction with others it should be
> noted that Agda's definitional equality is perhaps a bit weaker than
> one might want, because delayed expressions are compared by name.
> Given the definitions
>
> x ~ ♯ z
>
> and
>
> y ~ ♯ z
>
> x is not definitionally equal to y. In ΠΣ, Thorsten and Nicolas' core
> language, the (equivalent) expressions are definitionally equal
> because in ΠΣ boxes (expressions headed by ♯_) are compared for some
> notion of α-equality. As discussed by Thorsten this can lead to
> another problem with subject reduction
> (http://sneezy.cs.nott.ac.uk/fplunch/weblog/?p=104), so if Agda's
> definitional equality were strengthened one would have to take care to
> avoid this issue.
>
> Possible changes to Agda
> ------------------------
>
> You may have noticed that there are some rough edges to the approach
> outlined above. Some small changes to the way Agda handles coinduction
> would make it nicer. Specifically:
>
> • Remove codata and ~ from the language.
>
> • Make ∞, ♯_ and ♭ builtin (with rebindable notation, like sizes
> have
> now). Perhaps it is necessary to have one instance of this triple
> for every universe level, but it would be nice if this could be
> avoided.
>
> • Do not allow pattern matching on ♯_.
>
> • Treat ♯_ as a constructor when checking guardedness.
>
> • Insert a virtual ~ in front of every use of ♯_.
>
> If ~ could be used in let one could translate C [♯ e] to
> C [let r ~ ♯ e in r]. It would be nice if the user did not have to
> see the fresh variable r, though. This translation would also imply
> that the user has no direct way to refer to the delayed expression,
> which can be annoying.
>
> The first problem can perhaps be solved by writing out the
> right-hand side of the expression, instead of some fresh variable.
> However, this could be confusing to the user, because one could
> easily end up in a situation where Agda prints out the goal type as
> "e ≡ e", but the goal is nevertheless not provable. With a stronger
> equality between delayed expressions this situation could perhaps be
> avoided, though.
>
> These changes seem to be pretty easy to implement, unless we decide to
> switch to a different equality.
>
> --
> /NAD
>
>
> This message has been checked for viruses but the contents of an attachment
> may still contain software viruses, which could damage your computer system:
> you are advised to perform your own checks. Email communications with the
> University of Nottingham may be monitored as permitted by UK legislation.
>
> _______________________________________________
> Agda mailing list
> Agda at lists.chalmers.se
> https://lists.chalmers.se/mailman/listinfo/agda
>
More information about the Agda
mailing list