Haskell Type Names as Strings
I will introduce a few different ways to convert a type, from the type itself or from a value of a type, to a string. Here are all of the language pragmas and imports we will need.
{-# LANGUAGE DataKinds #-} -- support Symbol and type level lists
{-# LANGUAGE DeriveGeneric #-} -- derive Generic for Person
{-# LANGUAGE TypeFamilies #-} -- define type level operations
{-# LANGUAGE UndecidableInstances #-} -- allow `TypeName (Rep a ())` below
-- base
import GHC.Generics
import GHC.TypeLits (symbolVal, Symbol)
import Data.Proxy (Proxy (..))
import Data.Typeable
( typeOf
, typeRep
, typeRepTyCon
, tyConPackage
, tyConModule
, tyConName
)
-- typelits-witnesses
import GHC.TypeLits.List (symbolsVal)A type declaration we will use later.
data Person =
Person
{ name :: String
, age :: Int
} deriving (Generic)
type Human = PersonA type without a constructor.
data XGHC allows us to use strings as types. Also known as
Symbols. Symbols are useful when we calculate
types and we want to pass some string data along, maybe as a file path
or a route (servant does this), to another type. It is even possible to
perform operations on the string at the type level. We may want to look
at what the resulting string is by converting it to a value.
To work with types directly, and not the values of a particular type,
we must use Proxy :: Proxy ... to capture the type and pass
it to a function. This says we are referrencing a type, but not any
particular value of a type. A constructor is not required.
symbolVal converts the type level string to a value level
string. If you want to pass a type to a function and run
symbolVal on int, you must include
KnownSymbol a => ... as a constraint.
Runtime : Symbol to String
main :: IO ()
main = do
print $ symbolVal (Proxy :: Proxy "symbolVal") -- ("symbolVal" :: Symbol) to ("symbolVal" :: String)Using symbolsVal, we can convert a type level list of
type level strings to the value level.
print $ symbolsVal (Proxy :: Proxy '["first","second"]) -- ["first","second"]Runtime : Type Proxy to String
Get the type name of non-symbols. If you want to pass a type to a
function and run typeRep on in, you must include
TypeRep a => ... as a constraint.
print $ typeRep (Proxy :: Proxy Int) -- "Int"
print $ typeRep (Proxy :: Proxy Person) -- "Person"
print $ typeRep (Proxy :: Proxy X) -- "X"Get the type name of a value.
print $ typeOf "hello" -- "[Char]"
print $ typeOf $ Person "Susana" 40 -- "Person"typeRep returns TypeRep for a
Proxy of a type and typeOf returns
TypeRep for a value of a type. show on a
TypeRef. typeRepTyCon turns a
TypeRep into a TyCon and allows us to extract
more useful information.
let pTyCon = typeRepTyCon $ typeRep (Proxy :: Proxy Person)Package name where Person is defined.
print $ tyConPackage pTyCon -- "main"Module name where Person is defined.
print $ tyConModule pTyCon -- "Main"Type level constructor.
print $ tyConName pTyCon -- "Person"
print $ tyConName $ typeRepTyCon $ typeRep (Proxy :: Proxy Maybe) -- "Maybe" (it is not "Just" or "Nothing", those are value level constructorsCompile Time : Type to Symbol
Finally, there may be a case where you want to convert a type into
Symbol while you are still compiling. For example, for a
given type build a servant route base on the Symbol of the
type. GHC does not provide a way to automatically convert a type to
Symbol so we need to define our own type family to handle
this using GHC.Generics. For any type that does not have a
Generic instance, you must manually define what its
Symbol is.
type family TypeName a :: Symbol where
TypeName Double = "Double"
TypeName Int = "Int"
TypeName String = "String"
TypeName (M1 D ('MetaData name _ _ _) f ()) = name
TypeName a = TypeName (Rep a ()) -- requires UndecidableInstancesNow we can convert a type to a Symbol and the
Symbol to a String.
print $ symbolVal (Proxy :: Proxy (TypeName Int)) -- convert Int to ("Int" :: Symbol) to ("Int" :: String)
print $ symbolVal (Proxy :: Proxy (TypeName Person)) -- convert Person to ("Person" :: Symbol), using Generic, then to ("Int" :: String)
print $ symbolVal (Proxy :: Proxy (TypeName String))
print $ symbolVal (Proxy :: Proxy (TypeName Human))Notes:
Run with
stack runghc --package typelits-witnesses -- 2017-12-12-type-name-to-string.lhs.