Safe Haskell | Safe-Inferred |
---|---|
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 :: Type) (a :: Type) = Generically (Surgery' s a)
- type ProductSurgery (s :: Type) (a :: Type) = GenericProduct (Surgery' s a)
- type Surgeries (s :: [Type]) (a :: Type) = Surgery (Cat s) a
- type ProductSurgeries (s :: [Type]) (a :: Type) = ProductSurgery (Cat s) a
- newtype Surgery' (s :: Type) (a :: Type) = Surgery' {
- unSurgery' :: a
- type family GSurgery (s :: Type) (f :: k -> Type) :: k -> Type
- newtype Generically a = Generically a
- newtype GenericProduct a = GenericProduct 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 :: Type) :: Type
- 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 :: Type) :: Type
- 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 :: Type) @@ (s :: Symbol) :: Symbol
- data SId
- data SError
- data SConst (s :: Symbol)
- data SRename (xs :: [(Symbol, Symbol)]) (f :: Type)
- data OnFields (f :: Type -> Type) :: Type
- type DOnFields (f :: Type -> Type) (a :: Type) = Data (GSurgery (OnFields f) (Rep a)) ()
- data OnField (s :: Symbol) (f :: Type -> Type) :: Type
- type (%~) = OnField
- data Cat (ss :: [Type]) :: Type
- type DCat (ss :: [Type]) (a :: Type) = Data (GSurgery (Cat ss) (Rep a)) ()
- data CopyRep (a :: Type) :: Type
- 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 :: Type
- 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 :: Type
- 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 :: Type) (a :: Type) = 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 :: Type) (a :: Type) = GenericProduct (Surgery' s a) Source #
Apply a microsurgery s
to a type a
for DerivingVia
for the
Monoid
class.
type Surgeries (s :: [Type]) (a :: Type) = Surgery (Cat s) a Source #
Plural of Surgery
. Apply a list of microsurgeries.
type ProductSurgeries (s :: [Type]) (a :: Type) = ProductSurgery (Cat s) a Source #
Plural of ProductSurgery
. Apply a list of microsurgeries.
newtype Surgery' (s :: Type) (a :: Type) Source #
See Surgery
.
Surgery' | |
|
type family GSurgery (s :: Type) (f :: k -> Type) :: k -> Type 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 (Cat (s ': ss)) (g :: k -> Type) Source # | |
type GSurgery (Cat ('[] :: [Type])) (g :: k -> Type) Source # | |
Defined in Generic.Data.Internal.Microsurgery | |
type GSurgery (OnFields f) (g :: 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 (RenameFields rnm) (f :: k -> Type) Source # | |
Defined in Generic.Data.Internal.Microsurgery | |
type GSurgery (OnField s f) (g :: k -> Type) Source # | |
Defined in Generic.Data.Internal.Microsurgery |
newtype Generically a #
A datatype whose instances are defined generically, using the
Generic
representation. Generically1
is a higher-kinded version
of Generically
that uses Generic1
.
Generic instances can be derived via
using
Generically
A-XDerivingVia
.
{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingVia #-} import GHC.Generics (Generic) data V4 a = V4 a a a a deriving stock Generic deriving (Semigroup, Monoid) via Generically (V4 a)
This corresponds to Semigroup
and Monoid
instances defined by
pointwise lifting:
instance Semigroup a => Semigroup (V4 a) where (<>) :: V4 a -> V4 a -> V4 a V4 a1 b1 c1 d1 <> V4 a2 b2 c2 d2 = V4 (a1 <> a2) (b1 <> b2) (c1 <> c2) (d1 <> d2) instance Monoid a => Monoid (V4 a) where mempty :: V4 a mempty = V4 mempty mempty mempty mempty
Historically this required modifying the type class to include
generic method definitions (-XDefaultSignatures
) and deriving it
with the anyclass
strategy (-XDeriveAnyClass
). Having a /via
type/ like Generically
decouples the instance from the type
class.
Since: base-4.17.0.0
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
(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 # | |
(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 # | |
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 # | |
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 :: Type) :: Type 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 :: Type) :: Type 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 :: Type) @@ (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 :: Type) 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. Some applications of
this "higher-kindification" technique may be found 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:
data TwoCounters = MkTwoCounters { c1 :: Int, c2 :: Int } derivingGeneric
deriving (Semigroup
,Monoid
) via (ProductSurgery
(OnFields
Sum
) TwoCounters) -- Surgery here
Extensions and imports
{-# 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'
(..) -- )
type DOnFields (f :: Type -> Type) (a :: Type) = Data (GSurgery (OnFields f) (Rep a)) () Source #
Apply a type constructor f
to every field type of a type a
to make a
synthetic type.
data OnField (s :: Symbol) (f :: Type -> Type) :: Type Source #
Apply a type constructor f
to the field named s
in a generic record r
.
data Vec a = Vec { len :: Int , contents :: [a] } -- with (OnField "len" Sum) becomes -- data Vec a = Vec { len :: Sum Int , contents :: [a] }
This is a defunctionalized symbol, applied using GSurgery
or Surgery
.
See also the synonym (
.%~
)
type (%~) = OnField infixr 4 Source #
Infix name for OnField
. To be used with Surgeries
or Cat
.
Examples
Transform one Int
field into
for deriving Sum
IntMonoid
:
data Vec a = Vec { len :: Int , contents :: [a] } deriving Generic deriving (Eq, Show) via Generically (Vec a) deriving (Semigroup, Monoid) viaProductSurgeries
'["len"%~
Sum
] (Vec a)
Wrap unshowable fields in Opaque
for deriving Show
:
data Unshowable = Unshowable { fun :: Int -> Int , io :: IO Bool , int :: Int } deriving Generic deriving Show viaSurgeries
'["fun"%~
Opaque
, "io"%~
Opaque
] Unshowable -- show (Unshowable id (pure True) 42) = "Unshowable _ _ 42"
data Cat (ss :: [Type]) :: Type Source #
Compose surgeries together.
type DCat (ss :: [Type]) (a :: Type) = Data (GSurgery (Cat ss) (Rep a)) () Source #
Make a synthetic type (Data
) by chaining multiple surgeries.
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
data CopyRep (a :: Type) :: Type Source #
Change the generic representation to that of another type a
.
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 :: Type 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 #