| Copyright | (c) 2026 The Polytropos Authors |
|---|---|
| License | EUPL-1.2 OR Apache-2.0 |
| Maintainer | nadia.yvette.chambers@ik.me |
| Stability | experimental |
| Portability | non-portable (GHC extensions) |
| Safe Haskell | None |
| Language | GHC2021 |
Polytropos.Generics
Description
Generic deriving, representation bridging, and structural traversal engine for
polytropos.
This module bridges nominal Haskell algebraic data types (ADTs) to and from the
unified flat representation , and provides zero-overhead
structural operations (Rep, geq, gcompare, gshowsPrec, gmap)
across both open rows (gfoldMap, RowRepRecord, Variant) and nominal types.
Architectural Overview & Rationale
Why does polytropos-generics resemble built-in GHC.Generics rather than large-generics?
1. Unified Sums and Products vs. Records-Only
Libraries such as large-generics (Data.Record.Generic) are strictly
single-constructor record (product) libraries. Their representation
Rep f a = Rep (SmallArray (f Any)) lacks a constructor tag entirely,
preventing them from representing sum types, variants, or multi-constructor ADTs.
Furthermore, their dictionary abstraction dict :: Proxy c -> Rep (Dict c) a
is a 1D vector corresponding only to the fields of that single constructor.
In contrast, polytropos collapses products and sums into a single uniform mechanism:
data Rep (f :: Type -> Type) a = Rep {-# UNPACK #-} !Word !(SmallArray (f Any))
- In a product (record), the constructor tag is
0, and the array carries all fields. - In a sum (variant), the constructor tag corresponds to the constructor index, and the array carries that constructor's payloads.
Because types may have multiple constructors, dictionary resolution in polytropos-generics
is organized as a 2D dictionary matrix:
class GInstances (c :: Type -> Constraint) a where gdicts :: SmallArray (SmallArray (DictAny c))
The outer array is indexed in \(O(1)\) by the constructor tag tx :: Word; the inner array
supplies the field dictionaries for that specific constructor.
2. Flat Runtime Arrays vs. Binary Type-Tree Overhead
While standard GHC.Generics provides a simple Generic(from, to) mental model,
it compiles down to deeply nested binary type trees ((:*:), (:+:), M1, K1).
For types with many fields or constructors, this causes \(O(2^N)\) compile-time metadata
blow-up and substantial runtime pointer-chasing and heap allocation.
polytropos-generics adopts the intuitive Generic(from, to) interface, but
maps directly to flat contiguous memory. Generic operations run as tight unrolled loops
over SmallArray with literal dictionary dispatch.
When to Use This Module
- Whole-Data Operations on Open Rows: When you need structural equality (
), ordering (geq), rendering (gcompare), or uniform mapping (gshowsPrec,gmap) over opengfoldMapRecordorVarianttypes without writing manual boilerplate. - Bridging Nominal ADTs: When translating nominal Haskell data types into flat polytropos arrays for serialization (e.g. CBOR, Aeson), database marshalling (Hasql, Rel8, Beam), or open effect payload carrier systems.
When Not to Use This Module
- When working purely with individual field access and modification on open rows; use
the optics satellites (
polytropos-optics,polytropos-lens, orpolytropos-microlens) or the core field projection functions instead. - When working with records that require SOP n-ary product combinators (
NP,czipWith) over heterogeneous functors without sum representations; in those cases, specialized product generics may be preferred.
Worked Examples
Mode A: Whole-Data Operations over Open Rows
Open records and variants already provide built-in instances of Generic and
GInstances via the typechecker plugin's AllFields evidence synthesis:
{-# LANGUAGE DataKinds #-}
import Polytropos
import Polytropos.Generics
-- Create an open record using idiomatic REC syntax:
person :: Record '["name" ':= String, "age" ':= Int]
person = REC { name = "Alice", age = 30 }
-- 1. Structural equality:
isEqual :: Bool
isEqual = geq person person -- True
-- 2. Uniform type-directed mapping:
-- Double every numeric field in a record:
doubled :: Record '["x" ':= Int, "y" ':= Int]
doubled = gmap @Num (* 2) (REC { x = 10, y = 20 })
-- 3. Folding across heterogeneous fields:
summary :: String
summary = gfoldMap @Show (\x -> show x ++ "; ") person
-- Evaluates to: "\"Alice\"; 30; "Mode B: Bridging Nominal ADTs
Nominal Haskell types can implement Generic and GInstances to participate in
flat array-based generic routines:
{-# LANGUAGE TypeApplications #-}
import Polytropos.Core
import Polytropos.Generics
import Data.Primitive.SmallArray
import Unsafe.Coerce (unsafeCoerce)
data Shape = Circle Double | Rectangle Double Double
deriving (Show, Eq)
instance Generic Shape where
type RepOf Shape = Rep I Shape
from (Circle r) = Rep 0 (smallArrayFromList [I (unsafeCoerce r)])
from (Rectangle w h) = Rep 1 (smallArrayFromList [I (unsafeCoerce w), I (unsafeCoerce h)])
to (Rep 0 arr) = let I r = indexSmallArray arr 0 in Circle (unsafeCoerce r)
to (Rep 1 arr) = let I w = indexSmallArray arr 0; I h = indexSmallArray arr 1 in Rectangle (unsafeCoerce w) (unsafeCoerce h)
to _ = error "invalid constructor tag"
instance GInstances Eq Shape where
gdicts = smallArrayFromList
[ smallArrayFromList [mkDictAny @Eq @Double]
, smallArrayFromList [mkDictAny @Eq @Double, mkDictAny @Eq @Double]
]
-- Usage:
-- geq (Circle 5.0) (Circle 5.0) == TrueSynopsis
- class Generic a where
- newtype I a = I {
- unI :: a
- class GInstances (c :: Type -> Constraint) (a :: k) where
- gdicts :: SmallArray (SmallArray (DictAny c))
- geq :: (Generic a, RepOf a ~ Rep I a, GInstances Eq a) => a -> a -> Bool
- gcompare :: (Generic a, RepOf a ~ Rep I a, GInstances Ord a) => a -> a -> Ordering
- gshowsPrec :: (Generic a, RepOf a ~ Rep I a, GInstances Show a) => Int -> a -> ShowS
- gmap :: forall c a. (Generic a, RepOf a ~ Rep I a, GInstances c a) => (forall x. c x => x -> x) -> a -> a
- gfoldMap :: forall c a m. (Generic a, RepOf a ~ Rep I a, GInstances c a, Monoid m) => (forall x. c x => x -> m) -> a -> m
Generic Conversion Class
class Generic a where Source #
Generic conversion class. Bridges nominal ADTs to Rep.
Instances
| Generic (RowRep Identity 'ProductRowT r a) Source # | RowRep is an instance of Generic for product rows. | ||||
Defined in Polytropos.Generics Associated Types
Methods from :: RowRep Identity 'ProductRowT r a -> RepOf (RowRep Identity 'ProductRowT r a) Source # to :: RepOf (RowRep Identity 'ProductRowT r a) -> RowRep Identity 'ProductRowT r a Source # | |||||
| Generic (RowRep f 'ProductRowHKDT r a) Source # | RowRep is an instance of Generic for HKD product rows. | ||||
Defined in Polytropos.Generics Associated Types
Methods from :: RowRep f 'ProductRowHKDT r a -> RepOf (RowRep f 'ProductRowHKDT r a) Source # to :: RepOf (RowRep f 'ProductRowHKDT r a) -> RowRep f 'ProductRowHKDT r a Source # | |||||
| Generic (RowRep f 'SumRowHKDT s a) Source # | RowRep is an instance of Generic for HKD sum rows. | ||||
Defined in Polytropos.Generics Associated Types
Methods from :: RowRep f 'SumRowHKDT s a -> RepOf (RowRep f 'SumRowHKDT s a) Source # to :: RepOf (RowRep f 'SumRowHKDT s a) -> RowRep f 'SumRowHKDT s a Source # | |||||
| Generic (RowRep f 'SumRowT s a) Source # | RowRep is an instance of Generic for sum rows. | ||||
Defined in Polytropos.Generics | |||||
Dictionary Supply Matrix
class GInstances (c :: Type -> Constraint) (a :: k) where Source #
Provides an array of constraint dictionaries for each constructor of a,
and for each field within that constructor.
Methods
gdicts :: SmallArray (SmallArray (DictAny c)) Source #
Instances
| AllFields 'ProductRowT r c => GInstances c (RowRep Identity 'ProductRowT r a :: Type) Source # | GInstances for ProductRowT derived from AllFields. |
Defined in Polytropos.Generics Methods gdicts :: SmallArray (SmallArray (DictAny c)) Source # | |
| AllFields 'ProductRowHKDT r c => GInstances c (RowRep f 'ProductRowHKDT r a :: Type) Source # | GInstances for ProductRowHKDT derived from AllFields. |
Defined in Polytropos.Generics Methods gdicts :: SmallArray (SmallArray (DictAny c)) Source # | |
Generic Structural Operations
Equality and Ordering
geq :: (Generic a, RepOf a ~ Rep I a, GInstances Eq a) => a -> a -> Bool Source #
Generic equality using the Eq class.
gcompare :: (Generic a, RepOf a ~ Rep I a, GInstances Ord a) => a -> a -> Ordering Source #
Generic comparison using the Ord class.
Pretty Printing
gshowsPrec :: (Generic a, RepOf a ~ Rep I a, GInstances Show a) => Int -> a -> ShowS Source #
Generic showsPrec using the Show class.