HORIZON HASKELLDocslts/ghc-9.10.xc74966e2026-09-27Search names, modules, packages, or :: a typeCtrl K

GHC 9.10.3 · lts/ghc-9.10.x · c74966e · 2026-09-27

Modulegeneric-data-1.1.0.2Haskell2010

Generic.Data.Microsurgery

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 constructor f :: Type -> Type to every field.

  • CopyRep: use the generic representation of another type of the same shape.

  • Typeage: treat a newtype as a data 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 the Surgery or ProductSurgery type synonyms (for classes with instances for Generically or GenericProduct);

  • with the Data "synthetic type" for more involved transformations, for example using lenses in the next section.

  • 23 types
  • 13 values

Surgeries with generic-lens

0 declarations

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") 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)
    . toData

show (R 3) = "R {myField = _}"

Deriving via

8 declarations
familytype family GSurgery s (f :: k -> Type) :: k -> Type
#

Apply a microsurgery represented by a symbol s (declared as a dummy data type) to a generic representation f.

Instances9GSurgery, …
newtypenewtype 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 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.

Constructors

Instances11Bounded, Enum, Eq, Ord, Read, Show, …
newtypenewtype GenericProduct a
#

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.

Constructors

Instances4Generic, Semigroup, Monoid, Rep

Synthetic types

4 declarations
newtypenewtype Data (r :: Type -> Type) p
#

Synthetic data type.

A wrapper to view a generic Rep as the datatype it's supposed to represent, without needing a declaration.

Instances22Generic1, Monad, Functor, Applicative, Foldable, Traversable, …
  • Generic1 (Data r)Defined in generic-data-1.1.0.2 · Generic.Data.Internal.Data
  • Monad r => Monad (Data r)Defined in generic-data-1.1.0.2 · Generic.Data.Internal.Data
  • Functor r => Functor (Data r)Defined in generic-data-1.1.0.2 · Generic.Data.Internal.Data
  • Applicative r => Applicative (Data r)Defined in generic-data-1.1.0.2 · Generic.Data.Internal.Data
  • Foldable r => Foldable (Data r)Defined in generic-data-1.1.0.2 · Generic.Data.Internal.Data
  • Traversable r => Traversable (Data r)Defined in generic-data-1.1.0.2 · Generic.Data.Internal.Data
  • Alternative r => Alternative (Data r)Defined in generic-data-1.1.0.2 · Generic.Data.Internal.Data
  • MonadPlus r => MonadPlus (Data r)Defined in generic-data-1.1.0.2 · Generic.Data.Internal.Data
  • Eq1 r => Eq1 (Data r)Defined in generic-data-1.1.0.2 · Generic.Data.Internal.Data
  • Ord1 r => Ord1 (Data r)Defined in generic-data-1.1.0.2 · Generic.Data.Internal.Data
  • GShow1 r => Show1 (Data r)Defined in generic-data-1.1.0.2 · Generic.Data.Internal.Data
  • Contravariant r => Contravariant (Data r)Defined in generic-data-1.1.0.2 · Generic.Data.Internal.Data
  • GBounded r => Bounded (Data r p)Defined in generic-data-1.1.0.2 · Generic.Data.Internal.Data
  • GEnum StandardEnum r => Enum (Data r p)Defined in generic-data-1.1.0.2 · Generic.Data.Internal.Data
  • Eq (r p) => Eq (Data r p)Defined in generic-data-1.1.0.2 · Generic.Data.Internal.Data
  • Ord (r p) => Ord (Data r p)Defined in generic-data-1.1.0.2 · Generic.Data.Internal.Data
  • (GShow1 r, Show p) => Show (Data r p)Defined in generic-data-1.1.0.2 · Generic.Data.Internal.Data
  • (Functor r, Contravariant r) => Generic (Data r p)Defined in generic-data-1.1.0.2 · Generic.Data.Internal.Data
  • Semigroup (r p) => Semigroup (Data r p)Defined in generic-data-1.1.0.2 · Generic.Data.Internal.Data
  • Monoid (r p) => Monoid (Data r p)Defined in generic-data-1.1.0.2 · Generic.Data.Internal.Data
  • type Rep (Data r p) = rDefined in generic-data-1.1.0.2 · Generic.Data.Internal.Data
  • type Rep1 (Data r) = rDefined in generic-data-1.1.0.2 · Generic.Data.Internal.Data
valuetoData :: Generic a => a -> Data (Rep a) p
#

Conversion between a generic type and the synthetic type made using its representation. Inverse of fromData.

valueonData
  1. :: (UnifyRep r s, UnifyRep s r)
  2. => p (Data r x) (Data s y)
  3. -> p (Data r x) (Data s y)
#
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):

  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 same
renameConstrs @(SRename '[ '("Bar", "Baz") ] SId)
datadata RenameFields rnm
#

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.

Instances1GSurgery
datadata RenameConstrs rnm
#

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.

Instances1GSurgery

Renaming functions

familytype family (@@) f (s :: Symbol) :: Symbol
#

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"
Instances4@@
  • type (@@) SError s = TypeError ('Text "Invalid name: " ':<>: 'ShowType s)Defined in generic-data-1.1.0.2 · Generic.Data.Internal.Microsurgery
  • type (@@) SId s = sDefined in generic-data-1.1.0.2 · Generic.Data.Internal.Microsurgery
  • type (@@) (SConst z) _s = zDefined in generic-data-1.1.0.2 · Generic.Data.Internal.Microsurgery
  • type (@@) (SRename xs f) s = SRename' xs f sDefined in generic-data-1.1.0.2 · Generic.Data.Internal.Microsurgery
datadata SId
#

Identity function Symbol -> Symbol.

Instances1@@
  • type (@@) SId s = sDefined in generic-data-1.1.0.2 · Generic.Data.Internal.Microsurgery
datadata SError
#

Empty function (compile-time error when applied).

Instances1@@
datadata SConst (s :: Symbol)
#

Constant function.

Instances1@@
  • type (@@) (SConst z) _s = zDefined in generic-data-1.1.0.2 · Generic.Data.Internal.Microsurgery
datadata SRename (xs :: [(Symbol, Symbol)]) f
#

Define a function for a fixed set of strings, and fall back to f for the others.

Instances1@@
  • type (@@) (SRename xs f) s = SRename' xs f sDefined 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.

Example

Derive Semigroup and Monoid for a product of Num types:

data TwoCounters = MkTwoCounters { c1 :: Int, c2 :: Int }
  deriving Generic
  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'(..)        --
  )
datadata OnFields (f :: Type -> Type)
#

Apply a type constructor f to every field type of a generic representation r.

data Color = RGB
  { r :: Int
  , g :: Int
  , b :: Int }

-- becomes --

data Color f = RGB
  { r :: f Int
  , g :: f Int
  , b :: f Int }

This is a defunctionalized symbol, applied using GSurgery or Surgery.

Instances1GSurgery
datadata OnField (s :: Symbol) (f :: Type -> Type)
#

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 (%~).

Instances1GSurgery
  • type GSurgery (OnField s f) g = GOnField s f gDefined in generic-data-1.1.0.2 · Generic.Data.Internal.Microsurgery
typetype (%~) = OnField
#

Infix name for OnField. To be used with Surgeries or Cat.

Examples

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"
datadata Cat (ss :: [Type])
#

Compose surgeries together.

Instances2GSurgery
  • type GSurgery (Cat '[]) g = gDefined in generic-data-1.1.0.2 · Generic.Data.Internal.Microsurgery
  • type GSurgery (Cat (s ': ss)) g = GSurgery s (GSurgery (Cat ss) g)Defined in generic-data-1.1.0.2 · Generic.Data.Internal.Microsurgery

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 Data.Semigroup.<> z2
 = z1 * z2
 = Exp (modulus z1 * modulus z2) (argument z1 + argument z2)

mempty = 1 = Exp 1 0
datadata CopyRep a
#

Change the generic representation to that of another type a.

Instances1GSurgery
  • type GSurgery (CopyRep a) _1 = Rep aDefined in generic-data-1.1.0.2 · Generic.Data.Internal.Microsurgery

Type aging ("denewtypify")

datadata Typeage
#

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, applied using GSurgery or Surgery.

Instances1GSurgery

Derecordify

datadata Derecordify
#

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.

Instances1GSurgery