Understanding the categorical semantics of general inductive definitions in the HOTT book

228 Views Asked by At

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

There are 1 best solutions below

3
On BEST ANSWER

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.