Safe Haskell | None |
---|---|
Language | Haskell2010 |
Simple operations on generic representations, that only change the type-level metadata used by certain generic functions.
More complex ones can be found in generic-data-surgery but also, perhaps surprisingly, in generic-lens (read more about this just below) and one-liner.
Synopsis
- type Surgery (s :: *) (a :: *) = Generically (Surgery' s a)
- newtype Surgery' (s :: *) (a :: *) = Surgery' {
- unSurgery' :: a
- type family GSurgery (s :: *) (f :: k -> *) :: k -> *
- newtype Generically a = Generically {
- unGenerically :: 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 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
- 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 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)) ()
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
.
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"
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 (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
.
Generically | |
|
Instances
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.
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
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, to be applied using GSurgery
.
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 #
Type aging ("denewtypify")
Forget that a type is a newtype
. (The pun is that "aging" a type makes
it no longer "new".)
newtype Foo = Bar Baz -- becomes -- data Foo = Bar Baz
This is a defunctionalized symbol, to be applied using GSurgery
.
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, to be applied using GSurgery
.
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, to be applied using GSurgery
.
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
.