polytropos-generics
Copyright(c) 2026 The Polytropos Authors
LicenseEUPL-1.2 OR Apache-2.0
Maintainernadia.yvette.chambers@ik.me
Stabilityexperimental
Portabilitynon-portable (GHC extensions)
Safe HaskellNone
LanguageGHC2021

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 Rep, and provides zero-overhead structural operations (geq, gcompare, gshowsPrec, gmap, gfoldMap) across both open rows (RowRep, Record, 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 (geq), ordering (gcompare), rendering (gshowsPrec), or uniform mapping (gmap, gfoldMap) over open Record or Variant types 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, or polytropos-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) == True
Synopsis

Generic Conversion Class

class Generic a where Source #

Generic conversion class. Bridges nominal ADTs to Rep.

Associated Types

type RepOf a Source #

Methods

from :: a -> RepOf a Source #

to :: RepOf a -> a Source #

Instances

Instances details
Generic (RowRep Identity 'ProductRowT r a) Source #

RowRep is an instance of Generic for product rows.

Instance details

Defined in Polytropos.Generics

Associated Types

type RepOf (RowRep Identity 'ProductRowT r a) 
Instance details

Defined in Polytropos.Generics

type RepOf (RowRep Identity 'ProductRowT r a) = Rep I (RowRep Identity 'ProductRowT r a)

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.

Instance details

Defined in Polytropos.Generics

Associated Types

type RepOf (RowRep f 'ProductRowHKDT r a) 
Instance details

Defined in Polytropos.Generics

Generic (RowRep f 'SumRowHKDT s a) Source #

RowRep is an instance of Generic for HKD sum rows.

Instance details

Defined in Polytropos.Generics

Associated Types

type RepOf (RowRep f 'SumRowHKDT s a) 
Instance details

Defined in Polytropos.Generics

type RepOf (RowRep f 'SumRowHKDT s a) = Rep I (RowRep f 'SumRowHKDT s a)

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.

Instance details

Defined in Polytropos.Generics

Associated Types

type RepOf (RowRep f 'SumRowT s a) 
Instance details

Defined in Polytropos.Generics

type RepOf (RowRep f 'SumRowT s a) = Rep I (RowRep f 'SumRowT s a)

Methods

from :: RowRep f 'SumRowT s a -> RepOf (RowRep f 'SumRowT s a) Source #

to :: RepOf (RowRep f 'SumRowT s a) -> RowRep f 'SumRowT s a Source #

newtype I a Source #

Identity functor for use with Rep.

Constructors

I 

Fields

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.

Instances

Instances details
AllFields 'ProductRowT r c => GInstances c (RowRep Identity 'ProductRowT r a :: Type) Source #

GInstances for ProductRowT derived from AllFields.

Instance details

Defined in Polytropos.Generics

AllFields 'ProductRowHKDT r c => GInstances c (RowRep f 'ProductRowHKDT r a :: Type) Source #

GInstances for ProductRowHKDT derived from AllFields.

Instance details

Defined in Polytropos.Generics

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.

Functorial Mapping and Folding

gmap :: forall c a. (Generic a, RepOf a ~ Rep I a, GInstances c a) => (forall x. c x => x -> x) -> a -> a Source #

Generic map over all fields.

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 Source #

Generic foldMap over all fields.