Promoting the arrow type

Dependent Haskell is becoming reality. With GHC 8.0 we have GADT constructor promotion, which allows for new forms of type constructors, but doing functional programming on types remains awkward because the type-level analgoues of term-level functions–the type families–are not types at all. The first-class functions which make functional programming such a joy simply are not present at the type-level in GHC’s Haskell.

The 8.0 release permits a simple construction for making type families appear to be first-class. The idea is to proxy them with actual data types. For every type-level function, there is a type constructor, and a type family instance defined on that type constructor which actually implements the function. The simplest example is the identity function:

type Id = F IdProxy data IdProxy (x :: k) (p :: Proxy k) type instance EvalFunction (IdProxy x) = x

Type family proxies like IdProxy always follow a certain form: their final argument must be a Proxy which indicates the kind of thing it returns when evaluated with EvalFunction. This is plain in the definition of that family:

type family EvalFunction (d :: Proxy l -> Type) :: l

The type family F, used to define Id, is a kind of type-level smart constructor, which uses a type family proxy to come up with a type in the kind Function, which contains not only these promoted type families, but also the type constructors and promoted data constructors which already behave, more or less, like first-class functions:

data Function (s :: Type) (t :: Type) where Constructor :: (k -> l) -> Function s t Family :: (k -> l) -> Function s t infixr 0 :-> type s :-> t = Function s t

There’s another smart constructor C for type and data constructors. It’s no mystery why we need these: in either of the Function constructors there is no relationship between the arrow kinded type k -> l and the parameters s and t.

As function construction is handled by type families F and C, function deconstruction (application) is performed by a type family called At.

Id `At` 'True ~ 'True

With these ingredients, we can do functional programming with types:

type Plus = F PlusProxy data PlusProxy (n :: Nat) (m :: Nat) (p :: Proxy Nat) type instance EvalFunction (PlusProxy n m) = n + m type Foldr = F FoldrProxy data FoldrProxy (f :: a :-> b :-> b) (r :: b) (xs :: [a]) (p :: Proxy b) type instance EvalFunction (FoldrProxy f r '[]) = r type instance EvalFunction (FoldrProxy f r (x ': xs)) = f `At` x `At` (Foldr `At` f `At` r `At` xs) -- Six ~ 6 :: Nat type Six = Foldr `At` Plus `At` 0 `At` '[1,2,3]

We can even do ad-hoc polymorphism and give analogues of fmap, pure, <*>, and >>=. Check out the repository for more.