faking dependent types

Are the terms really breaking through the colon?

Not exactly. What she's doing has rather more of the flavour of Omega, which allows you to declare datakinds — kinds containing type-level data, kept separate from run-time data. She retains the separation of type-level data from run-time data, but saves you the effort of declaring datakinds by recycling your datatypes. Just put a type in {..}, the braces of upward mobility, and it's a kind! Now put expressions built from constructors and variables in {..} to shift them up to the type level.

Here's an example:

data Nat = Z | S Nat
 
data Vec :: {Nat} -> * -> * where
  VNil :: Vec {Z} x
  (:>) :: x -> Vec {n} x -> Vec {S n} x

Now you can write stuff like.

vapp :: Vec {n} (s -> t) -> Vec {n} s -> Vec {n} t
vapp VNil VNil = VNil
vapp (f :> fs) (s :> ss) = f s :> vapp fs ss

type level programs

By a miracle of stunning cheapness, this old pal also goes with the new gal:

type family (m :: {Nat}) :+ (n :: {Nat}) :: {Nat}
type instance {Z} :+ n = n
type instance {S m} :+ n = {S} (m :+ n)
 
vappend :: Vec m x -> Vec n x -> Vec (m :+ n) x
vappend VNil ys = ys
vappend (x :> xs) ys = x :> vappend xs ys

Mark you well that :+ lives in the type language, so it's got to be {S} (m :+ n), not {S (m :+ n)}. The latter would treat :+ as a lifted data constructor.

all fur coat and no knickers

Of course, it's a fiddle. Let's look at what she gives to ghc.

data Nat = Z | S Nat

data Vec :: * -> * -> * where
  VNil :: Vec (SheTyZ) x
  (:>) :: x -> Vec (n) x -> Vec (SheTyS n) x

vapp :: Vec (n) (s -> t) -> Vec (n) s -> Vec (n) t
vapp VNil VNil = VNil
vapp (f :> fs) (s :> ss) = f s :> vapp fs ss

type family (m :: *) :+ (n :: *) :: *
type instance (SheTyZ) :+ n = n
type instance (SheTyS m) :+ n = (SheTyS) (m :+ n)

vappend :: Vec m x -> Vec n x -> Vec (m :+ n) x
vappend VNil ys = ys
vappend (x :> xs) ys = x :> vappend xs ys

data SheTyZ = SheTyZ
data SheTyS x1 = SheTyS x1
data SheTyVNil = SheTyVNil
data (:$#$#$#:>) x1 x2 = (:$#$#$#:>) x1 x2

She erases fancy kinds to *, and translates data constructors to type constructors with ugly names. She also declares type-level versions of your data constructors, with said ugly names. Note, if you want to use infix data constructors, you'll need {-# LANGUAGE TypeOperators #-} to permit the type-level version she generates.

Note that she puts lots of brackets in. Better safe than sorry. Also, you get to write {} for {()} and {x,y} for {(x,y)}, and so on.

lifting standard stuff

You can find ShePrelude with some precooked goodies.

pi kinds

Undertested, but you should be able to write dependent kinds like

pi (x :: *)(n :: {Nat}). {Vec {n} x} -> *
desugaring to * -> * -> * -> *, of course!

gremlins

  • You can write type variables in {..} kinds, but explicit forall is not properly implemented yet.
  • Try making Vec {n} an Applicative instance and learn something!
  • Name mangling is just dumb, so it's easy to engineer clashes.
  • Erasure means no nice kind-checking. Edwin says I should transform kind-checking problems into the analogous type-checking problems and fix them up with #line pragmas to boot. Simon says we should just implement it properly in GHC.
  • She doesn't lift literals.

a more accurate picture?