In addition to the promoted and singled versions of the Show class that
singletons-base provides, it is also useful to be able to directly define
Show instances for singleton types themselves. Doing so is almost entirely
straightforward, as a derived Show instance does 90 percent of the work.
The last 10 percent—getting the right instance context—is a bit tricky, and
that's where ShowSing comes into play.
As an example, let's consider the singleton type for lists. We want to write an instance with the following shape:
instance ??? => Show (SList (z :: [k])) where
showsPrec p SNil = showString "SNil"
showsPrec p (SCons sx sxs) =
showParen (p > 10) $ showString "SCons " . showsPrec 11 sx
. showSpace . showsPrec 11 sxs
To figure out what should go in place of ???, observe that we require the
type of each field to also be Show instances. In other words, we need
something like (Show (Sing (a :: k))). But this isn't quite right, as the
type variable a doesn't appear in the instance head. In fact, this a
type is really referring to an existentially quantified type variable in the
SCons constructor, so it doesn't make sense to try and use it like this.
Luckily, the QuantifiedConstraints language extension provides a solution
to this problem. This lets you write a context of the form
(forall a. Show (Sing (a :: k))), which demands that there be an instance
for Show (Sing (a :: k)) that is parametric in the use of a.
This lets us write something closer to this:
instance (forall a. Show (Sing (a :: k))) => SList (Sing (z :: [k])) where ...
The ShowSing class is a thin wrapper around
(forall a. Show (Sing (a :: k))). With ShowSing, our final instance
declaration becomes this:
instance ShowSing k => Show (SList (z :: [k])) where ...
In fact, this instance can be derived:
deriving instance ShowSing k => Show (SList (z :: [k]))
(Note that the actual definition of ShowSing is slightly more complicated than what this documentation might suggest. For the full story, refer to the documentation for ShowSing'.)
When singling a derived Show instance, singletons-th will also generate
a Show instance for the corresponding singleton type using ShowSing.
In other words, if you give singletons-th a derived Show instance, then
you'll receive the following in return:
A promoted (
PShow) instanceA singled (
SShow) instanceA Show instance for the singleton type
What a bargain!