| Safe Haskell | None |
|---|---|
| Language | Haskell2010 |
Generic.Data.Microsurgery
Description
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 -> Typeto every field.CopyRep: use the generic representation of another type of the same shape.Typeage: treat anewtypeas adatatype.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
DerivingViaextension, using theSurgeryorProductSurgerytype synonyms (for classes with instances forGenericallyorGenericProduct); - 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 OpaqueOpaque 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 } deriving Generic
Example usage
We derive an instance of Show that hides the "myField" field,
whatever its type.
instanceShowR whereshowsPrecn =gshowsPrecn .onData(over (field_ @"myField")Opaque) .toDatashow(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 }
deriving Show 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.
Constructors
| Surgery' | |
Fields
| |
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
>>>:set -XDerivingVia -XDeriveGeneric>>>:{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.
>>>:{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) :}
Constructors
| Generically | |
Fields
| |
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
>>>:set -XDeriveGeneric -XDerivingVia>>>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.
Constructors
| GenericProduct | |
Fields
| |
Instances
| Generic a => Generic (GenericProduct a) Source # | |
Defined in Generic.Data.Internal.Generically Associated Types type Rep (GenericProduct a) :: Type -> Type # Methods 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 Methods (<>) :: 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 Methods 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(Repa) p ->Data(F (Repa)) p unf ::Data(F (Repa)) p ->Data(Repa) 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 .toDatafromData. 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 same
renameConstrs @(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 }
deriving Generic
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 }
deriving Generic
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 ZapConcretely, 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 #