Safe Haskell | None |
---|---|
Language | Haskell2010 |
Simple operations on generic representations:
modify Generic
instances to tweak the behavior of generic
implementations as if you had declared a slightly different type.
This module provides the following microsurgeries:
RenameFields
: rename the fields of a record type.RenameConstrs
: rename the constructors.OnFields
: apply a type constructorf :: Type -> Type
to every field.CopyRep
: use the generic representation of another type of the same shape.Typeage
: treat anewtype
as adata
type.Derecordify
: treat a type as if it weren't a record.
More complex surgeries can be found in generic-data-surgery but also, perhaps surprisingly, in generic-lens (read more about this just below) and one-liner.
Surgeries can be used:
- to derive type class instances with the
DerivingVia
extension, using theSurgery
orProductSurgery
type synonyms (for classes with instances forGenerically
orGenericProduct
); - with the
Data
"synthetic type" for more involved transformations, for example using lenses in the next section.
Synopsis
- type Surgery (s :: *) (a :: *) = Generically (Surgery' s a)
- type ProductSurgery (s :: *) (a :: *) = GenericProduct (Surgery' s a)
- newtype Surgery' (s :: *) (a :: *) = Surgery' {
- unSurgery' :: a
- type family GSurgery (s :: *) (f :: k -> *) :: k -> *
- newtype Generically a = Generically {
- unGenerically :: a
- newtype GenericProduct a = GenericProduct {
- unGenericProduct :: a
- data Data r p
- toData :: Generic a => a -> Data (Rep a) p
- fromData :: Generic a => Data (Rep a) p -> a
- onData :: (UnifyRep r s, UnifyRep s r) => p (Data r x) (Data s y) -> p (Data r x) (Data s y)
- data RenameFields (rnm :: *) :: *
- renameFields :: forall rnm f p. Coercible (GSurgery (RenameFields rnm) f) f => Data f p -> Data (GSurgery (RenameFields rnm) f) p
- unrenameFields :: forall rnm f p. Coercible (GSurgery (RenameFields rnm) f) f => Data f p -> Data (GSurgery (RenameFields rnm) f) p
- data RenameConstrs (rnm :: *) :: *
- renameConstrs :: forall rnm f p. Coercible (GSurgery (RenameConstrs rnm) f) f => Data f p -> Data (GSurgery (RenameConstrs rnm) f) p
- unrenameConstrs :: forall rnm f p. Coercible (GSurgery (RenameConstrs rnm) f) f => Data f p -> Data (GSurgery (RenameConstrs rnm) f) p
- type family (f :: *) @@ (s :: Symbol) :: Symbol
- data SId
- data SError
- data SConst (s :: Symbol)
- data SRename (xs :: [(Symbol, Symbol)]) (f :: *)
- data OnFields (f :: * -> *) :: *
- type DOnFields (f :: * -> *) (a :: *) = Data (GSurgery (OnFields f) (Rep a)) ()
- data CopyRep (a :: *) :: *
- copyRep :: forall a f p. Coercible (GSurgery (CopyRep a) f) f => Data f p -> Data (GSurgery (CopyRep a) f) p
- uncopyRep :: forall a f p. Coercible f (GSurgery (CopyRep a) f) => Data (GSurgery (CopyRep a) f) p -> Data f p
- data Typeage :: *
- typeage :: Coercible (GSurgery Typeage f) f => Data f p -> Data (GSurgery Typeage f) p
- untypeage :: Coercible f (GSurgery Typeage f) => Data (GSurgery Typeage f) p -> Data f p
- data Derecordify :: *
- derecordify :: Coercible (GSurgery Derecordify f) f => Data f p -> Data (GSurgery Derecordify f) p
- underecordify :: Coercible f (GSurgery Derecordify f) => Data (GSurgery Derecordify f) p -> Data f p
Surgeries with generic-lens
One common and simple situation is to modify the type of some fields, for example wrapping them in a newtype.
We can leverage the generic-lens
library, with the two functions below.
-- Lens to a field named fd
in a Generic record.
field_ :: HasField_ fd s t a b => Lens s t a b -- from generic-lens
-- Update a value through a lens (ASetter is a specialization of Lens).
over :: ASetter s t a b -> (a -> b) -> s -> t -- from lens or microlens
For example, here is a record type:
data R = R { myField :: Int } deriving Generic
The function over (field_ @"myField")
applies the newtype constructor Opaque
Opaque
to the field
"myField"
, but this actually doesn't typecheck as-is. With a bit of help
from this module, we can wrap that function as follows:
onData
(over (field_ @"myField")Opaque
) .toData
:: R ->Data
_ _ -- type arguments hidden
The result has a type
, that from the point of view of GHC.Generics
looks just like Data
_ _R
but with the field "myField"
wrapped in
Opaque
, as if we had defined:
data R = R { myField ::Opaque
Int } derivingGeneric
Example usage
We derive an instance of Show
that hides the "myField"
field,
whatever its type.
instanceShow
R whereshowsPrec
n =gshowsPrec
n .onData
(over (field_ @"myField")Opaque
) .toData
show
(R 3) = "R {myField = _}"
Deriving via
type Surgery (s :: *) (a :: *) = Generically (Surgery' s a) Source #
Apply a microsurgery s
to a type a
for DerivingVia
.
For the Monoid
class, see ProductSurgery
.
Example
{-# LANGUAGE DerivingVia #-} -- The constructors must be visible. import Generic.Data.Microsurgery (Surgery
,Surgery'
(..),Generically
(..),Derecordify
) data T = T { unT :: Int } derivingShow
via (Surgery
Derecordify
T) -- T won't be shown as a record: -- show (T {unT = 3}) == "T 3"
type ProductSurgery (s :: *) (a :: *) = GenericProduct (Surgery' s a) Source #
Apply a microsurgery s
to a type a
for DerivingVia
for the
Monoid
class.
newtype Surgery' (s :: *) (a :: *) Source #
See Surgery
.
Surgery' | |
|
type family GSurgery (s :: *) (f :: k -> *) :: k -> * Source #
Apply a microsurgery represented by a symbol s
(declared as a dummy data
type) to a generic representation f
.
Instances
type GSurgery Derecordify (f :: k -> Type) Source # | |
Defined in Generic.Data.Internal.Microsurgery | |
type GSurgery Typeage (M1 D ('MetaData nm md pk _nt) f :: k -> Type) Source # | |
type GSurgery (CopyRep a) (_1 :: Type -> Type) Source # | |
type GSurgery (RenameFields rnm) (f :: k -> Type) Source # | |
Defined in Generic.Data.Internal.Microsurgery | |
type GSurgery (RenameConstrs rnm) (f :: k -> Type) Source # | |
Defined in Generic.Data.Internal.Microsurgery | |
type GSurgery (OnFields f) (g :: k -> Type) Source # | |
Defined in Generic.Data.Internal.Microsurgery |
newtype Generically a Source #
Type with instances derived via Generic
.
Examples
Deriving Eq
, Ord
, Show
, Read
>>>
:{
data T = C Int Bool deriving Generic deriving (Eq, Ord, Show, Read) via (Generically T) :}
Deriving Semigroup
, Monoid
The type must have only one constructor.
>>>
import Data.Monoid (Sum)
>>>
:{
data U = D [Int] (Sum Int) deriving Generic deriving (Semigroup, Monoid) via (Generically U) :}
Deriving Enum
, Bounded
The type must have only nullary constructors.
To lift that restriction, see FiniteEnumeration
.
>>>
:{
data V = X | Y | Z deriving Generic deriving (Eq, Ord, Enum, Bounded) via (Generically V) :}
Generically | |
|
Instances
newtype GenericProduct a Source #
Product type with generic instances of Semigroup
and Monoid
.
This is similar to Generically
in most cases, but
GenericProduct
also works for types T
with deriving
via
, where GenericProduct
UU
is a generic product type coercible to,
but distinct from T
. In particular, U
may not have an instance of
Semigroup
, which Generically
requires.
Example
>>>
import Data.Monoid (Sum(..))
>>>
data Point a = Point a a deriving Generic
>>>
:{
newtype Vector a = Vector (Point a) deriving (Semigroup, Monoid) via GenericProduct (Point (Sum a)) :}
If it were via
instead, then
Generically
(Point (Sum a))Vector
's mappend
(the Monoid
method) would be defined as Point
's
(
(the <>
)Semigroup
method), which might not exist, or might not be
equivalent to Vector
's generic Semigroup
instance, which would be
unlawful.
Instances
Generic a => Generic (GenericProduct a) Source # | |
Defined in Generic.Data.Internal.Generically type Rep (GenericProduct a) :: Type -> Type # from :: GenericProduct a -> Rep (GenericProduct a) x # to :: Rep (GenericProduct a) x -> GenericProduct a # | |
(AssertNoSum Semigroup a, Generic a, Semigroup (Rep a ())) => Semigroup (GenericProduct a) Source # | |
Defined in Generic.Data.Internal.Generically (<>) :: GenericProduct a -> GenericProduct a -> GenericProduct a # sconcat :: NonEmpty (GenericProduct a) -> GenericProduct a # stimes :: Integral b => b -> GenericProduct a -> GenericProduct a # | |
(AssertNoSum Semigroup a, Generic a, Monoid (Rep a ())) => Monoid (GenericProduct a) Source # | |
Defined in Generic.Data.Internal.Generically mempty :: GenericProduct a # mappend :: GenericProduct a -> GenericProduct a -> GenericProduct a # mconcat :: [GenericProduct a] -> GenericProduct a # | |
type Rep (GenericProduct a) Source # | |
Defined in Generic.Data.Internal.Generically |
Synthetic types
Synthetic data type.
A wrapper to view a generic Rep
as the datatype it's supposed to
represent, without needing a declaration.
Instances
toData :: Generic a => a -> Data (Rep a) p Source #
Conversion between a generic type and the synthetic type made using its
representation. Inverse of fromData
.
onData :: (UnifyRep r s, UnifyRep s r) => p (Data r x) (Data s y) -> p (Data r x) (Data s y) Source #
onData :: _ => (Data r x -> Data s y) -> (Data r x -> Data s y) -- possible specialization
Can be used with generic-lens
for type-changing field updates with field_
(and possibly other generic optics).
A specialization of the identity function to be used to fix types
of functions on Data
, unifying the "spines" of input and output generic
representations (the "spine" is everything except field types, which may
thus change).
Microsurgeries
Each microsurgery consists of a type family F
to modify metadata in
GHC Generic representations, and two mappings (that are just
coerce
):
f ::Data
(Rep
a) p ->Data
(F (Rep
a)) p unf ::Data
(F (Rep
a)) p ->Data
(Rep
a) p
Use f
with toData
for generic functions that consume generic values,
and unf
with fromData
for generic functions that produce generic
values. Abstract example:
genericSerialize . f .toData
fromData
. unf . genericDeserialize
Renaming of fields and constructors
These surgeries require DataKinds
and TypeApplications
.
Examples
{-# LANGUAGE DataKinds, TypeApplications #-} -- Rename all fields to "foo"renameFields
@(SConst
"foo") -- Rename constructor "Bar" to "Baz", and leave all others the samerenameConstrs
@(SRename
'[ '("Bar", "Baz") ]SId
)
data RenameFields (rnm :: *) :: * Source #
Rename fields using the function rnm
given as a parameter.
data Foo = Bar { baz :: Zap } -- becomes, renaming "baz" to "bag" -- data Foo = Bar { bag :: Zap }
This is a defunctionalized symbol, applied using GSurgery
or Surgery
.
Instances
type GSurgery (RenameFields rnm) (f :: k -> Type) Source # | |
Defined in Generic.Data.Internal.Microsurgery |
renameFields :: forall rnm f p. Coercible (GSurgery (RenameFields rnm) f) f => Data f p -> Data (GSurgery (RenameFields rnm) f) p Source #
unrenameFields :: forall rnm f p. Coercible (GSurgery (RenameFields rnm) f) f => Data f p -> Data (GSurgery (RenameFields rnm) f) p Source #
data RenameConstrs (rnm :: *) :: * Source #
Rename constructors using the function rnm
given as a parameter.
data Foo = Bar { baz :: Zap } -- becomes, renaming "Bar" to "Car" -- data Foo = Car { baz :: Zap }
This is a defunctionalized symbol, applied using GSurgery
or Surgery
.
Instances
type GSurgery (RenameConstrs rnm) (f :: k -> Type) Source # | |
Defined in Generic.Data.Internal.Microsurgery |
renameConstrs :: forall rnm f p. Coercible (GSurgery (RenameConstrs rnm) f) f => Data f p -> Data (GSurgery (RenameConstrs rnm) f) p Source #
unrenameConstrs :: forall rnm f p. Coercible (GSurgery (RenameConstrs rnm) f) f => Data f p -> Data (GSurgery (RenameConstrs rnm) f) p Source #
Renaming functions
type family (f :: *) @@ (s :: Symbol) :: Symbol Source #
f @@ s
is the application of a type-level function symbolized by f
to a s ::
.Symbol
A function FooToBar
can be defined as follows:
data FooToBar
type instance FooToBar @@
"foo" = "bar"
Empty function (compile-time error when applied).
data SRename (xs :: [(Symbol, Symbol)]) (f :: *) Source #
Define a function for a fixed set of strings, and fall back to f
for the others.
Wrap every field in a type constructor
Give every field a type f FieldType
(where f
is a parameter), to
obtain a family of types with a shared structure. This
"higher-kindification" technique is presented in the following
blogposts:
- https://www.benjamin.pizza/posts/2017-12-15-functor-functors.html
- https://reasonablypolymorphic.com/blog/higher-kinded-data/
See also the file test/one-liner-surgery.hs
in this package for an
example of using one-liner and generic-lens with a synthetic type
constructed with DOnFields
.
Example
Derive Semigroup
and Monoid
for
a product of Num
types:
{-# LANGUAGE DeriveGeneric, DerivingVia #-} import Data.Monoid (Sum
(..)) -- Constructors must be in scope import GHC.Generics (Generic
) import Generic.Data.Microsurgery (ProductSurgery
,OnFields
,GenericProduct
(..) -- Constructors must be in scope ,Surgery'
(..) -- ) data TwoCounters = MkTwoCounters { c1 :: Int, c2 :: Int } derivingGeneric
deriving (Semigroup
,Monoid
) via (ProductSurgery
(OnFields
Sum
) TwoCounters) -- Surgery here
type DOnFields (f :: * -> *) (a :: *) = Data (GSurgery (OnFields f) (Rep a)) () Source #
Apply a type constructor to every field type of a type a
to make a
synthetic type.
Substitute a generic representation from another type
Example
Derive Semigroup
and Monoid
for
a product of Num
types, but using Sum
for one
field and Product
for the other.
In other words, we use the fact that Polar a
below is isomorphic to
the monoid (
.Product
a, Sum
a)
{-# LANGUAGE DeriveGeneric, DerivingVia #-} import Data.Monoid (Sum
(..),Product
(..)) -- Constructors must be in scope import GHC.Generics (Generic
) import Generic.Data.Microsurgery (ProductSurgery
,CopyRep
,GenericProduct
(..) -- Constructors must be in scope ,Surgery'
(..) -- ) data Polar a = Exp { modulus :: a, argument :: a } derivingGeneric
deriving (Semigroup
,Monoid
) via (ProductSurgery
(CopyRep
(Product
a,Sum
a)) (Polar a)) -- Surgery here
That is the polar representation of a complex number:
z = modulus * exp(i * argument)
The product of complex numbers defines a monoid isomorphic to
the monoid product (Product Double, Sum Double)
(multiply the moduli, add the arguments).
z1<>
z2 = z1*
z2 = Exp (modulus z1*
modulus z2) (argument z1+
argument z2)mempty
= 1 = Exp 1 0
copyRep :: forall a f p. Coercible (GSurgery (CopyRep a) f) f => Data f p -> Data (GSurgery (CopyRep a) f) p Source #
uncopyRep :: forall a f p. Coercible f (GSurgery (CopyRep a) f) => Data (GSurgery (CopyRep a) f) p -> Data f p Source #
Type aging ("denewtypify")
Derecordify
data Derecordify :: * Source #
Forget that a type was declared using record syntax.
data Foo = Bar { baz :: Zap } -- becomes -- data Foo = Bar Zap
Concretely, set the last field of MetaCons
to False
and forget field
names.
This is a defunctionalized symbol, applied using GSurgery
or Surgery
.
Instances
type GSurgery Derecordify (f :: k -> Type) Source # | |
Defined in Generic.Data.Internal.Microsurgery |
derecordify :: Coercible (GSurgery Derecordify f) f => Data f p -> Data (GSurgery Derecordify f) p Source #
underecordify :: Coercible f (GSurgery Derecordify f) => Data (GSurgery Derecordify f) p -> Data f p Source #