Type-Level Literals

Currently, we support two forms of type-level literals: natural numbers, and symbol constants.
Natural number literals are a family of types, all of which belong to the kind Nat. Similarly,
symbol literals are types that belong to the kind Symbol:

0, 1, 2, ... :: Nat
"hello", "world", "some string literal" :: Symbol

Both numeric and symbol literal types are empty---they have no inhabitants. However, they may be
used as parameters to other type constructors, which makes them useful.

Singleton Types

We use this idea to link the type-level literals to specific run-time values via singleton types.
The singleton types and some useful functions for working with them are defined in module GHC.TypeLits:

module GHC.TypeLits where

A singleton type is simply a type that has only one interesting inhabitant. We define a whole family
of singleton types, parameterized by type-level literals:

data Sing :: a -> *

For example, Sing 0, Sing 127, Sing "hello", and Sing "this also" are all
singleton types. The intuition is that the only inhabitant of Sing n is the value n. Notice
that Sing has a polymorphic kind because sometimes we apply it to numbers (which are of
kind Nat) and sometimes we apply it to symbols (which are of kind Symbol).

But, if we have a value of type Sing a, how do we get the actual integer or string?
We can do this with the overloaded function fromSing. Its type is quite general because
it can support various singleton families, but for the purposes of this explanation
it is sufficient t

The function fromSing has an interesting type: it maps singletons to ordinary values,
but the type of the result depends on the kind of the singleton parameter.
So, if we apply it to a value of type Sing 3 we get the number3, but,
if we apply it to a value of type Sing "hello" we get the string"hello".

So, how do we make values of type Sing n in the first place? This is done with
the special overloaded constant sing:

The first function, withSing, is useful when we want to work with the same singleton value multiple times.
The constant sing is polymorphic, so every time we use it in a program, it may refer to a potentially
different singleton, so to ensure that two singleton values are the same we have to resort to
explicit type signatures, which just adds noise to a definition. By using, withSing we avoid this problem
because we get an explicit (monomorphic) name for a given singleton, and so we can use the name many times
without any type signatures. This technique is shown in the definition of the second function, singThat.

The function singThat is similar to the constant sing in that it defines new singleton values. However,
it allows us to specify a predicate on (the representation of) the value and it only succeeds if this predicate
holds. Here are some examples of how that works:

This style is, basically, a more typed version of what is found in many standard C libraries.
Callers of this function have to pass the size of the array explicitly, and the type system checks that the
size matches that of the array. Note that in the implementation of memset_c we used fromSing
to get the concrete integer associated with the singleton type, so that we know how many elements
to set with the given value/

While the explicit Sing parameter is convenient when we define the function, it is a bit
tedious to have to provide it all the time---it is easier to let the compiler infer the value,
based on the type of the array: