lazy decision
Install the singletons package:
{-
And you're done.
Unfortunately, the library is rarely documented and quite complex. I recommend viewing the additional project page for additional documentation. I am trying to explain the basics below.
Sing is a data family that defines singleton representations. Singletones are structurally the same as disparate types, but their values are indexed by the corresponding raised values. For example, singleton data Nat = Z | S Nat data Nat = Z | S Nat will be
data instance Sing (n :: Nat) where SZ :: Sing Z SS :: Sing n -> Sing (S n)
singletons is a template function that generates singletones (and also removes derived instances and can also raise functions).
SingKind is essentially a good class that provides us with the type Demote and fromSing . Demote gives us the corresponding non-overlapped type for the raised value. For example, Demote False is Bool , and Demote "foo" is Symbol . fromSing converts a singleton value to the corresponding unallocated value. So, fromSing SZ is equal to Z
SingI is a class that reflects raised values into solid values. Sing is its method, and sing :: Sing x gives the same value of x . This is almost what we want; to complete the definition of reflect we need to use fromSing on Sing to get an unallocated value.
KProxy - Export Data.Proxy . This allows us to capture view variables from the environment and use them within definitions. Please note that instead of KProxy you can use any type of supported type (* → *). For more on data type promotion, see This.
Note that there is a weaker form KProxy by type, for which KProxy is not required:
type family Demote (a :: k) type instance Demote (s :: Symbol) = String type instance Demote (b :: Bool) = Bool
So far so good, but how can we write an instance for the removed lists?
type instance Demote (xs :: [a]) = [Demote ???]
Demote a not allowed, of course, because a is a view, not a type. Therefore, we need KProxy to be able to use a on the right side.
Stand alone solution
This happens similarly to the singletons solution, but we intentionally skip single point representations and go directly to reflection. This should be somewhat more productive, and we could even learn a little in this process (I, of course, did!).
import GHC.TypeLits import Data.Proxy data Tree a = Node a [Tree a] deriving (Eq, Show)
We implement type dispatching of the open type and provide a type synonym for convenience:
type family Demote' (kparam :: KProxy k) :: * type Demote (a :: k) = Demote' ('KProxy :: KProxy k)
The 'KProxy :: KProxy k picture is that we use 'KProxy :: KProxy k whenever we want to specify the form k .
type instance Demote' ('KProxy :: KProxy Symbol) = String type instance Demote' ('KProxy :: KProxy (Tree a)) = Tree (Demote' ('KProxy :: KProxy a)) type instance Demote' ('KProxy :: KProxy [a]) = [Demote' ('KProxy :: KProxy a)]
Doing the reflection pretty right now:
class Reflect (a :: k) where reflect :: Proxy (a :: k) -> Demote a instance KnownSymbol s => Reflect (s :: Symbol) where reflect = symbolVal instance Reflect ('[] :: [k]) where reflect _ = [] instance (Reflect x, Reflect xs) => Reflect (x ': xs) where reflect _ = reflect (Proxy :: Proxy x) : reflect (Proxy :: Proxy xs) instance (Reflect n, Reflect ns) => Reflect (Node n ns) where reflect _ = Node (reflect (Proxy :: Proxy n)) (reflect (Proxy :: Proxy ns))