Type-Level Induction in Haskell

Posted on May 5, 2018

The code from this post is available as a gist.

One of the most basic tools for use in type-level programming is the Peano definition of the natural numbers:

data ℕ = Z | S ℕ

Using the new TypeFamilyDependencies extension, these numbers can be used to describe the “size” of some type. I’m going to use the proportion symbol here:

type family (t ∷ k) ∝ (n ∷ ℕ) = (a ∷ Type ) | a → t n k (tk) ∝ (nℕ)(at n k

Using this type family we can describe induction on the natural numbers:

class Finite n where ∷ t ∝ Z → ( ∀ k . t ∝ k → t ∝ S k) → t ∝ n inductiont ∝t ∝ kt ∝k)t ∝ n instance Finite Z where = z induction z _ {-# inline induction #-} instance Finite n ⇒ Finite ( S n) where n) = s (induction z s) induction z ss (induction z s) {-# inline induction #-}

The induction function reads as the standard mathematical definition of induction: given a proof (value) of the zero case, and a proof that any proof is true for its successor, we can give you a proof of the case for any finite number.

An added bonus here is that the size of something can usually be resolved at compile-time, so any inductive function on it should also be resolved at compile time.

We can use it to provide the standard instances for basic length-indexed lists:

infixr 5 :- data List n a where n a Nil ∷ List Z a ( :- ) ∷ a → List n a → List ( S n) a n an) a

Some instances for those lists are easy:

instance Functor ( List n) where n) fmap _ Nil = Nil fmap f (x :- xs) = f x :- fmap f xs f (xxs)f xf xs

However, for Applicative , we need some way to recurse on the size of the list. This is where induction comes in.

type instance '( List ,a) ∝ n = List n a '(,a) ∝ nn a

This lets us write pure in a pleasingly simple way:

instance Finite n ⇒ Applicative ( List n) where n) pure x = induction Nil (x :- ) induction(x

But can we also write <*> using induction? Yes! Because we’ve factored out the induction itself, we just need to describe the notion of a “sized” function:

data a ↦ b a ↦ b type instance ((x ∷ a) ↦ (y ∷ b)) ∝ n = (x ∝ n) → (y ∝ n) ((xa) ↦ (yb)) ∝ n(x ∝ n)(y ∝ n)

Then we can write <*> as so:

instance Finite n ⇒ Applicative ( List n) where n) pure x = induction Nil (x :- ) induction(x ( <*> ) = induction Nil Nil → Nil ) (\ :- fs) (x :- xs) → f x :- k fs xs) (\k (ffs) (xxs)f xk fs xs)

What about the Monad instance? For that, we need a little bit of plumbing: the type signature of >>= is:

( >>= ) ∷ m a → (a → m b) → m b m a(am b)m b

One of the parameters (the second a ) doesn’t have a size: we’ll need to work around that, with Const :

type instance ( Const a ∷ ℕ → Type ) ∝ n = Const a n ) ∝ na n

Using this, we can write our Monad instance:

∷ List ( S n) a → a head'n) a :- _) = x head' (x_) ∷ List ( S n) a → List n a tail'n) an a :- xs) = xs tail' (_xs)xs instance Finite n ⇒ Monad ( List n) where n) >>= (f ∷ a → List n b) = xs(fn b) induction Nil _ → Nil ) (\ :- ys) fn → head' (fn ( Const y)) :- (\k (yys) fnhead' (fn (y)) . fn . Const . getConst)) k ys (tail'fngetConst)) xs . getConst ∷ Const a n → List n b) (fgetConsta nn b)

Type Family Dependencies