Hello folks,
Have higher coinductive types have gotten any/much attention? A google search reveals no work on this.
We can define normal coinductive types fairly easily, by analogy to inductive types. For example, streams of natural numbers:
coinductive Stream : Type {
peek : Stream -> Nat
next : Stream -> Stream
}
Here,
peek and
next are destructors of
Stream, rather than constructors. (I'm using my own syntax here.) We end up with a corecursive principle (to define functions into
Stream):
Stream_corec : {A:Type} -> (A -> Nat) -> (A -> A) -> (A -> Stream)
And a bisimulation prunciple (to define dependant functions into paths in
Stream):
Stream_bisim : {R : Stream -> Stream -> Type}
-> ({a b : Stream} -> R(a,b) -> (peek(a) = peek(b)))
-> ({a b : Stream} -> R(a,b) -> R(next(a), next(b)))
-> ({a b : Stream} -> R(a,b) -> (a = b))
(Is there a more direct analogue to the induction principle for coinductive types?)
For higher coinductive types, we are allowed to specify higher destructors, that take values from paths. For example...
pathValue : {a b : T} -> (a = b) -> Nat
We end up with a corecursive principle:
T_corec : {A : Type} -> (A -> Nat) -> ({a b : A} -> (a = b) -> Nat) -> (A -> T)
And a bisimulation principle. This time, since R(a,b) is supposed to represent (some) paths from a to b, we must have a pathValue for every R(a,b). So, the bisimulation requires a function of type R(a,b) -> Nat for every a,b:
T_bisim : {R : T -> T -> Type}
-> ({a b : T} -> R(a,b) -> (pointValue(a) = pointValue(b)))
-> ({a b : T} -> R(a,b) -> Nat)
-> ({a b : T} -> R(a,b) -> (a = b))
So what is this good for? I'm not sure. But here are three things to consider.
1. [Higher] Coinductive types may warrant a higher bisimulation principle -- one between higher paths. When are two paths equal? When they have the same behavior. The path behavior is dictated by its value (as given by higher destructors), and by its behavior in the groupoid.
2. Higher destructors can be used to forcibly separate two or more points in a higher coinductive type, dually to how higher constructors can be used to forcibly identify two or more points in an inductive type. For example, we can define the type of infinite binary trees in which the left and right subtree must be different:
coinductive DiffBinTree : Type {
node : DiffBinTree -> Nat
leftT : DiffBinTree -> DiffBinTree
rightT : DiffBinTree -> DiffBinTree
mustBeDiff : {a : DiffBinTree} -> (leftT(a) = rightT(a)) -> 0
}
The trick, of course, is to have certain kinds of path lead to contradiction.
3. The groupoid structure of types can be exploited to define some interesting objects. For example, the type of algebraic groups over a base type A:
coinductive Group : Type -> Type {
value : {A : Type} {a b : Group A} -> (a = b) -> A
Now each inhabitant of Group A represents an algebraic group whose members are inhabitatnts of type A. The group structure is actually present at the path level. Each point g : Group A has a group (g = g), in which reflexivity is the identity, symmetry is the inverse, and transitivity is multiplication.
What do you folks think?
Thank you for reading.
Cheers,
Francisco Mota