Search code examples
functional-programmingagdadependent-typetheorem-provingtype-theory

Mutually Inductive Descriptions with different type indices?


I'm using Descriptions, like they are described here, as a way of encoding the shape of inductive data types. However, I'm stuck on how to represent inductive types that are:

  1. Mutually inductive
  2. Have different indices

For example, suppose we've got something like this, where we're ordering different types:

data Foo : Set where
  N : ℕ -> Foo 
  P : (Foo × Foo) -> Foo

data <N : ℕ -> ℕ -> Set where
  <0 : (n : ℕ) -> <N zero n
  <suc : (m n : ℕ) -> <N m n -> <N (suc m) (suc n)

data <P : (Foo × Foo) -> (Foo × Foo) -> Set 
data <F : Foo -> Foo -> Set 

data <P where
  <Pair : (x1 x2 y1 y2 : Foo) -> <F x1 y1 -> <F x2 y2 -> <P (x1 , x2) (y1 , y2)

data <F where
  <FN : ∀ (a b : ℕ) -> <N a b -> <F (N a) (N b)
  <FP : ∀ (p1 p2 : (Foo × Foo) ) -> <P p1 p2 -> <F (P p1) (P p2)

That is, we've got a type of binary trees with nats at the leaves. We have a partial order between trees, where we compare nats in the usual way, and we compare pairs of nodes by comparing their respective subtrees.

Notice how <F and <P are mutually dependent on each other. Of course, we could inline this to make <F and <P one type, I'm trying to avoid that, for cases where <P is more complicated.

What I'm wondering is: can the above partial order types be expressed using Descriptions?

I get stuck even trying to describe the above types as the fixed-point of an indexed functor. Usually we have an index type (I : Set) and the functor has type (X : I -> Set) -> I -> Set. But we can't have both "Foo" and "Foo × Foo" be our I value. Is there some trick that allows us to use I = Foo ⊎ (Foo × Foo)?


Solution

  • Take the sum of all of the indices:

    Ix = ((Foo × Foo) × (Foo × Foo)) ⊎ (Foo × Foo)
    
    data <P : Ix -> Set
    data <F : Ix -> Set
    
    data <P where
      <Pair : (x1 x2 y1 y2 : Foo) -> <F (inj₂(x1 , y1)) -> <F (inj₂(x2 , y2))
                                  -> <P (inj₁((x1 , x2), (y1 , y2)))
    
    data <F where
      <FN : ∀ (a b : ℕ) -> <N a b -> <F (inj₂(N a , N b))
      <FP : ∀ (p1 p2 : (Foo × Foo) ) -> <P (inj₁(p1 , p2)) -> <F (inj₂(P p1 , P p2))
    

    We can tidy up the indices a bit more, and write things in a form which is more obviously describable by indexed functors:

    data Ix : Set where
      <P : Foo × Foo → Foo × Foo → Ix
      <F : Foo → Foo → Ix
    
    data T : Ix → Set where
      <Pair : ∀ x1 x2 y1 y2  → T (<F x1 y1) → T (<F x2 y2)
                             → T (<P (x1 , x2) (y1 , y2))
      <FN : ∀ (a b : ℕ) → <N a b → T (<F (N a) (N b))
      <FP : ∀ p1 p2 → T (<P p1 p2) → T (<F (P p1) (P p2))
    

    I note though that <P and <F can be defined recursively, so induction is not essential here.

    <F : Foo → Foo → Set
    <F (N n) (N n')              = <N n n'
    <F (P (x , y)) (P (x' , y')) = <F x x' × <F y y'
    <F (N _) (P _) = ⊥
    <F (P _) (N _) = ⊥