Type promotion

Type promotion was introduced in the same paper as kind polymorphism (Giving Haskell a Promotion, by Yorgey et al in 2012). This represented a major leap forward for Haskell's type-level programming capabilities.

Let's explore type promotion in the context of a type-level programming example. We want to create a list where the type itself contains information about the list size.

To represent numbers at type level, we use the age-old Peano numbering which describes the natural numbers (1,2,3, ...) in a recursive manner:

  data Zero = Zero
    deriving Show
  data Succ n = Succ n
    deriving Show

  one = Succ Zero
  two = Succ one

We'll use this with the understanding that certain bad expressions are still allowed:

  badSucc1 = Succ 10    -- :: Succ Int
  badSucc2 = Succ False -- :: Succ Bool

Our size-aware list type Vec is represented as a GADT:

-- requires
-- {-# LANGUAGE GADTs #-}
-- {-# LANGUAGE KindSignatures #-}

data Vec :: (* -> * -> *) where
  Nil  :: Vec a Zero
  Cons :: a -> Vec a n -> Vec a (Succ n)

nil' = Nil :: Vec Int Zero

cons1 = Cons 3 nil'
--      :: Vec Int (Succ Zero)

cons2 = Cons 5 cons1
--      :: Vec Int (Succ (Succ Zero))

The Vec data type has two type parameters: the first represents the list data and the second represents the list size, that is, either Zero or Succ (although this is not enforced by the type-checker).

The Cons data constructor increments the list size as part of the type signature. Every time we cons an element to a list, a different list type is returned, and thereby the list size is incremented. Instead of a size function defined on term-level, we now have a type-level function.

Unfortunately the following is valid:

  badVec = Nil :: Vec Zero Zero

However, the following is not valid:

  -- badVec2 = Nil :: Vec Zero Bool -- INVALID

We need more type-safety when working with kinds, and for that, we need datatypes on the kind level. That is precisely what the DataKinds extension enables.

Promoting types to kinds

The Vec data type expresses type-level programming, but with a very blunt tool, where kinds only describe arity of types and little more.

The DataKinds language extension promotes all (suitable) datatypes to kind level in such a way that we can use the types as kinds in kind signatures. This gives us type-safety at the kind-level. Let's continue with the example from the previous section. First, we unify Zero and Succ into the Nat datatype, as follows:

-- {-# LANGUAGE DataKinds #-}
data Nat = ZeroD | SuccD Nat

This gives us more type-safety:

  badSuccD = SuccD 10 -- INVALID

The DataKinds language extension will automatically promote the Nat type to the Nat kind. The data-constructors ZeroD and SuccD are promoted to types.

The Vec datatype uses type promotion along with kind polymorphism. The second type parameter is now constrained to be of kind Nat:

data VecD :: * -> Nat -> * where
  NilD :: VecD a 'ZeroD
  ConsD :: a -> VecD a n -> VecD a ('SuccD n)

cons1D = ConsD 3 NilD
--    :: VecD Integer ('SuccD 'ZeroD)

cons2D = ConsD '5' NilD
--    :: VecD Char ('SuccD 'ZeroD)

Promoted types and kinds can be prefixed with a quote ' to unambiguously specify the promoted type or kind.

The type signature for ConsD uses type 'SuccD (promoted from the datatype constructor SuccD). Similarly, NilD uses the type 'ZeroD.

We created custom kinds by promoting custom datatypes, then we used them to express constrained kind signatures.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset