We use a family of singleton types to relate type-level naturals to runtime values.

In our design, we chose to provide as primitive an overloaded "smart" constructor and a polymorphic elimination construct:

newtype Sing n = Sing (SingRep n)

class SingI n where
  sing :: Sing n

instance SingI 0 where sing = Sing 0
instance SingI 1 where sing = Sing 1

fromSing :: Sing n -> SingRep n
fromSing (Sing n) = n

It is also possible to make the dual choice, where we provide a polymorphic constructor and an overloaded elimination construct:

data Sing n = Sing

class SingE n where
  fromSing :: Sing n -> SingRep n

instance NatE 0 where fromSing Sing = 0
instance NatE 1 where fromSing Sing = 1

We made this choice, at least in part, because it made the implementation simpler: with our choice the evidence for class SingI is just an integer or a string. Note that our choice does not loose any generality because we can define the alternative design in terms of it:

data Sing1 = Sing1

fromSing1 :: SingI n => Sing1 n -> SingRep n
fromSing1 = fromSing . cast
  where cast :: SingI n => Sing1 n -> Sing n
        cast Sing1 = sing
Last modified 7 years ago Last modified on Apr 9, 2012 2:20:05 AM