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:
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
The function over (field_ @"myField") Generic.Data.Opaque
applies the newtype constructor Generic.Data.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") Generic.Data.Opaque) . toData
:: R -> Data _ _ -- type arguments hidden
The result has a type Data _ _, that from the point of view of GHC.Generics
looks just like R but with the field "myField" wrapped in
Generic.Data.Opaque, as if we had defined:
data R = R { myField :: Generic.Data.Opaque Int } deriving Generic
Example usage
We derive an instance of Show that hides the "myField" field,
whatever its type.
instance Show R where
showsPrec n = Generic.Data.gshowsPrec n
. onData (over (field_ @"myField") Generic.Data.Opaque)
. toDatashow (R 3) = "R {myField = _}"
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 Generically A using
-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.
Product type with generic instances of Semigroup and Monoid.
This is similar to Generic.Data.Generically in most cases, but
GenericProduct also works for types T with deriving
via GenericProduct U, where U is a generic product type coercible to,
but distinct from T. In particular, U may not have an instance of
Semigroup, which Generic.Data.Generically requires.
Example
Example3 expressions
>>> 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 Generic.Data.Generically (Point (Sum a)) instead, then
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.
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
0 declarations
Each microsurgery consists of a type family F to modify metadata in
GHC Generic representations, and two mappings (that are just
coerce):
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)
Define a function for a fixed set of strings, and fall back to f for the others.
Instances1@@
type (@@) (SRenamexsf) s = SRename'xsfsDefined in generic-data-1.1.0.2 · Generic.Data.Internal.Microsurgery
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:
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.
Transform one Int field into Sum Int for deriving Monoid:
data Vec a = Vec
{ len :: Int
, contents :: [a] }
deriving Generic
deriving (Eq, Show) via Generically (Vec a)
deriving (Semigroup, Monoid) via ProductSurgeries '["len" %~Sum] (Vec a)
Wrap unshowable fields in Generic.Data.Opaque for deriving Show:
data Unshowable = Unshowable
{ fun :: Int -> Int
, io :: IO Bool
, int :: Int }
deriving Generic
deriving Show via Surgeries '["fun" %~Generic.Data.Opaque, "io" %~Generic.Data.Opaque] Unshowable
-- show (Unshowable id (pure True) 42) = "Unshowable _ _ 42"
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).
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).