| Safe Haskell | None |
|---|---|
| Language | Haskell2010 |
Generic.Data.Microsurgery
Contents
Description
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 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.
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"
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 (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.
Constructors
| Generically | |
Fields
| |
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(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
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, 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 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, 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.