First-class instances
GHC compiles type classes as data types. first-class-instaces makes that
correspondence explicit with a simple but powerful mechanism to define instances.
This enables implementations of deriving as libraries.
Overview
Type classes as data types
Type classes are associated to data types using the mkDict
command.
Example:
class Monoid a where
mempty :: a
(<>) :: a -> a -> a
mkDict ''Monoid
That generates the following data type:
data DictMonoid a = Monoid
{ _mempty :: a
, (|<>) :: a -> a -> a
}
Dictionaries
The Dict
type family maps constraints to their corresponding dictionary types.
type family Dict (c :: Constraint) :: Type -- Defined in FCI
type instance Dict (Monoid a) = DictMonoid a -- also from mkDict ''Monoid
A constraint c
can be reflected as a dictionary:
dict @c :: c => Dict c
Instances from dictionaries
A dictionary d
can be reified as an instance.
Example:
instanceDict [| d :: Dict (Monoid T) |]
That generates the following instance:
instance Monoid T where
mempty = _mempty d
(<>) = (|<>) d
Show me the types
The core API of first-class-instances:
{- module FCI -}
type Dict :: Constraint -> Type
dict :: forall c. c => Dict c
mkDict :: Name -> Q [Dec]
instanceDict :: Q Type -> Q [Dec]
{- module FCI.Unsafe -}
(==>) :: forall c r. Dict c -> (c => r) -> r
Deriving as a library
These simple primitives can be used to implement "deriving as a library".
The adventure continues in ad-lib.
-
The reflection library
is another approach to convert constraints into first-class values,
and back. It provides a solution for implicit configuration, rather than
deriving type class instances for user-defined types.
-
In Agda,
all data types are type classes,
exemplifying the orthogonality of declaring a data type, and passing values
implicitly as instances.
Haskell requires instances to be globally unique: there must not be two instances
Monoid T
for the same T
.