0
$\begingroup$

In the homotopy type theory book in section 5.6, "the general syntax of inductive definitions", an example of a valid constructor for an inductive definition is given:

$$ c : (A \to W) \to (B \to C \to W) \to D \to W \to W $$

However, I don't see from this constructor type why a function of type

$$ d : (A \to W) \to (A \to P) \to (B \to C \to W) \to (B \to C \to P) \to D \to W \to P \to P $$

is needed to give the recursion principle (when attempting to define a function $f : W \to P$ recursively) for the inductively defined type $W$ with generator $c$. Given the material in section 5.4, it seems to me like the function $d$ should actually have the form

$$ d : (A \to P) \to (B \to C \to P) \to D \to P \to P $$

since $(P,d)$ would then be an $F$-algebra, where in this case $$ F(X) = (A \to X) \to (B \to C \to X) \to D \to X $$ with the map defined on morphisms in the evident way, and assuming our inductively defined type $(W,c)$ is an initial $F$-algebra, we can define $f : W \to P$ as the unique $F$-algebra morphism from $(W,c)$ to $(P,d)$.

Why is the $d$ defined in the book different from what the categorical semantics would seem to suggest as the proper data required to define a function $f : W \to P$ recursively?

  • 1
    I stumbled across the paper [Dependent Inductive and Coinductive Types are Fibrational Dialgebras](http://eptcs.web.cse.unsw.edu.au/paper.cgi?FICS2015.3.pdf) which discusses inductive and coinductive data types (a la Agda) in terms of initial and final dialgebras in fibrations.2017-02-27

1 Answers 1

2

If you have pairs, these are equivalent. Just set $P$ to $W\times P$ in your version of $d$ and you'll see that at least the types are isomorphic. To use some older FP terminology, the distinction is between a paramorphism and a catamorphism. Now we might say between a recursor and a fold. $c$ corresponds to the following constructor for some data type (using Haskell syntax) which, for clarity, I'll assume is the only constructor:

data W = Constr (A -> W) (B -> C -> W) D W

A fold would then look like:

foldW :: ((A -> x) -> (B -> C -> x) -> D -> x -> x) 
      -> W
      -> x
foldW f (Constr aw bcw d w) = 
    f (\a -> foldW f (aw a))
      (\b c -> foldW f (bcw b c))
      d 
      (foldW f w)

A recursor would look like:

recW :: ((A - W) -> (A -> x) -> (B -> C -> W) -> (B -> C -> x) -> D -> W -> x -> x) 
     -> W
     -> x
recW f (Constr aw bcw d w) = 
    f aw 
      (\a -> recW f (aw a))
      bcw 
      (\b c -> recW f (bcw b c))
      d 
      w
      (recW f w)

So what's happening here is that we're passing the subterms both recursively processed, like in foldW and unprocessed. Note the f parameter corresponds to the existence of the $d$ function in both your and the book's variant in each of the definitions respectively. It will probably be clearer for a less arbitrary data type.

data N = Zero | Succ N

foldN :: x -> (x -> x) -> N -> x
foldN z s Zero = z
foldN z s (Succ n) = s (foldN z s n)

recN :: x -> (N -> x -> x) -> N -> x
recN z s Zero = z
recN z s (Succ n) = s n (recN z s n)

Note that recN corresponds to primitive recursion, but also note that we're just additionally passing the predecessor to the s function in recN. We can obviously define foldN/foldW in terms of recN/recW. The other way is also not too difficult.

recN :: x -> (N -> x -> x) -> N -> x
recN z s n = snd (foldN (Zero, z) (\(m, x) -> (Succ m, s m x)) n)

recW :: ((A - W) -> (A -> x) -> (B -> C -> W) -> (B -> C -> x) -> D -> W -> x -> x) 
     -> W
     -> x
recW f w = 
    snd (foldW 
          (\awx bcwx d (w', x) -> 
              (Constr (\a -> fst (awx a)) (\b c -> fst (bcwx b c)) d w',
               f (\a -> fst (awx a)) 
                 (\a -> snd (awx a)) 
                 (\b c -> fst (bcwx b c)) 
                 (\b c -> snd (bcwx b c)) 
                 d 
                 w' 
                 x))  
          w)

So if you have pairs you can just continue to use initial algebra semantics. Without pairs things are more complicated which is why primitive recursion is a recursor and not a fold which would be more natural from a categorical perspective. It should also be clear why, even with pairs, we usually want to have the recursor rather than the fold as the recursor is easier to use and requires far less computation for many common patterns (e.g. "pattern matching"). To calculate the predecessor of a natural using foldN requires time and space linear in the natural and is slightly non-obvious to write. Using recN, it's constant time and trivial to write.

  • 0
    Interesting! So, essentially, the recursor for a data type is like the fold, expect the "pattern matching data" is explicitly passed to the functions giving the recursive definition. I know it's not part of my original question, but do you think you could comment on the analogous situation for co-inductive definitions? I can pretty easily write "unfold" functions on codata, but I'm having trouble dualising to see how to write "corecursors", say for the type of infinite lists given by the eliminators $h : List \; A \to A$ and $t : List \; A \to List \; A$.2017-02-25
  • 0
    I guess in this specific instance it must be something like $corec : (T \to A) \to (T \to (T + List \; A)) \to T \to List \; A$, correct?2017-02-25
  • 0
    More or less, yes. The term for the dual of a paramorphism is an apomorphism, where an unfold is an anamorphism to use the Squiggolist terminology. It is indeed the case that you return a sum type that lets you immediately complete the corecursion. Much like the recursor, this can significantly reduce the calculation needed, e.g. implementing stream cons is straightforward with a corecursor (apomorphism) but with an unfold (anamorphism) you essentially inefficiently "copy" the tail.2017-02-26