[Agda] Coinductive families

Thorsten Altenkirch txa at Cs.Nott.AC.UK
Tue Oct 5 11:41:58 CEST 2010


On 4 Oct 2010, at 23:45, Dan Doel wrote:

> On Monday 04 October 2010 1:49:11 pm Thorsten Altenkirch wrote:
>> I think it is a good idea if we distinguish two issues:
>> 
>> 1) The definitions of datatypes by constructors or destructors
>>     corresponding to labelled sums (data) and labelled products (codata)
>> 2) The definition of recursive datatypes as either inductive or coinductive
>>     corresponding to initial algebras or terminal coalgebras.
>> 
>> E.g. sometimes it is more natural to represent coinductive datatypes in
>> their constructor representations, eg. for colists. Also decoupling the
>> two allows a flexible syntax for mixed inductive / coinductive datatypes
>> (see Nisse's and main recent papers on this).
> 
> How well does this work, though? It seems like there are problems with it.

I tried to guess what "it" is. :-)

Reading on, it seems that you are saying that the termination checker should be
aware of destructors in the sense that a destructor should make things structurally 
smaller but that this would lead to non-termination (for open terms).

Is this a sensible summary? I don't have a good answer, actually I haven't thought of this.

Thorsten

> The 
> correspondence of inductive types with data as labelled sums is, of course, 
> due to some mangling of the algebra action:
> 
>  in : F A -> A
>    ~ F A = F1 A + F2 A + ...
>  (c1 : F1 A -> A) * (c2 : F2 A -> A) * ...
> 
> If we instead want to define an inductive type by its destructors, it's a 
> roundabout process.
> 
>  F A = F1 A * F2 A * ...
>  in : F A -> A
>  cata : (F B -> B) -> A -> B
>  cata (map in) : A -> F A
>  d_i = proj_i . cata (map in) : A -> Fi A
> 
> and the projections are only really justified in light of the general 
> eliminator. Importantly, the projections don't directly give you a basis for 
> writing inductively justified functions. The bug report I mentioned had an 
> example of this:
> 
>  record R : Set where
>    destructor
>      p : R -> R
> 
>  foo : R -> R
>  foo r = foo (p r)
> 
> Inductively this is justified, but it won't normalize. Really, if we expect 
> records to be inductive, we probably have to perform the *reverse* 
> translation: projections into constructors (and lose eta?).
> 
>  foo r = foo (p r) ==> foo (record { r }) = foo r
> 
> This is the form that is (obviously) justified by the induction principle, and 
> doesn't have normalization problems.
> 
>> In any case, I think it is a good idea to dualize our current way to define
>> indexed datatypes by indexed constructors to indexed records defined by
>> destructors as Dan and Andreas suggest. To add another example, we can
>> define Vec using destructors:
>> 
>> record Vec (A : Set) : ℕ → Set where
>>  destructor
>>    hd : ∀ {n} → Vec A (suc n) → A
>>    tl : ∀ {n} → Vec A (suc n) → Vec A n
> 
> This can have some oddness, too:
> 
>  foldr : {A R : Set} {n : Nat} -> (A -> R -> R) -> R -> Vec A n -> R
>  foldr {n = 0}     f z l = z
>  foldr {n = suc m} f z l = f (hd l) (foldr f z (tl l))
> 
> if we want to use the projections, we are required to do induction on n, 
> otherwise we'll fail to normalize, l won't have the right type, we'll have no 
> base case.... What even is the constructor-based definition?
> 
>  foldr f z (record {hd = x ; tl = xs}) = f x (foldr f z xs)
>  foldr f z ???                         = z
> 
> The inductive family is sort of split into disjoint injections by an accident 
> of the index, but I don't know if this is good enough to base elimination 
> around. I think typically the underlying theory is like (neglecting the slice 
> stuff and using families):
> 
>  F : (Nat -> Set) -> Nat -> Set
>  F X 0 = Top
>  F X n = Bot
> 
>  G : (Nat -> Set) -> Nat -> Set
>  G X 0       = Bot
>  G X (suc n) = A * X n
> 
> And then Vec A = initial algebra of (F + G), meaning there's a clear way to 
> split even on an arbitrary family (and then you erase the bottom cases). The 
> inductive codata instead looks like:
> 
>  F : (Nat -> Set) -> Nat -> Set
>  F X 0       = Top
>  F X (suc n) = A * X n
> 
> So it is less obvious that the family itself is an initial algebra of a sum of 
> functors on the category of families. Recognizing it in general would probably 
> require decomposing arbitrary functors in the right way (we could, of course, 
> decompose it in many ways, but it may not correspond to the constructors we'd 
> want). This isn't necessarily an academic question, either, because the 
> checker presumably needs to rewrite the inductive-projections into inductive-
> matching before it can decide how/whether the termination of the function is 
> justified; so it'd need to be done for foldr above, even though it's 
> ultimately unnecessary.
> 
> Coinduction and constructors also have impedence mismatches, as Agda's history 
> with it has shown. Most notably, the sums/constructors being defined are for 
> the result of the observation, and so matching on them doesn't make sense in 
> the usual inductive, dependent pattern matching way.
> 
> If we wanted to really make this work, it'd probably be necessary to have 
> destructor and constructor clauses for both inductive and coinductive types:
> 
>  data Vec (A : Set) : Nat -> Set where
>    constructor
>      []   : Vec A 0
>      _::_ : {n : Nat} -> A -> Vec A n -> Vec A (suc n)
>    destructor
>      head : {n : Nat} -> Vec A (suc n) -> A
>      tail : {n : Nat} -> Vec A (suc n) -> Vec A n
> 
> where, for inductives, destructor-based code is rewritten into constructor-
> based code, and constructors cannot be elided, and the opposite is true for 
> coinductives. I see that Andreas Abel has something like that from his message 
> that recently arrived. I note that the above sort of definition requires that 
> we verify that the shape defined by the constructors matches that defined by 
> the destructors, as well; I'm not sure how hard that is in general.
> 
> 
> I'm kind of skeptical about citing the mixed induction and coinduction, too, 
> because what Agda has (and has had for some time) is not exactly coinduction. 
> If it were, then ∞ would just be the identity functor, and writing:
> 
>  data T : Set where
>     t : ∞ T -> T
> 
> would not result in a coinductive type, and we'd be able to write:
> 
>  foo : T → ⊥
>  foo (t x) = foo (♭ x)
>  -- ♭ x <= x < t x
> 
> There's probably a theory underlying how Agda works (and the stuff Nisse* and 
> others have built on it is impressive), but coinduction isn't (obviously) it.
> 
>> Dually to the fact that we can encode indexed datatypes using equality and
>> sigma-types, we can can encode these indexed records using eqaulity and
>> pi-types, namely
>> 
>> record Vec (A : Set)(n : ℕ) : Set where
>>  field
>>    hd : ∀ {m} → n ≡ suc m → A
>>    tl : ∀ {m} → n ≡ suc m → Vec A m
> 
> Yeah. I was thinking about this, but for some reason I was thinking there'd 
> have to be a sigma in there. It can be curried away, clearly.
> 
> This, I guess, gives credence to the idea that the indices quantified over 
> aren't a problem.
> 
> -- Dan
> 
> [*] Have I been Doing It Wrong by spelling it Nils? They are the same person, 
> right? If so, I'm sorry.



More information about the Agda mailing list