| 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 | Trustworthy |
| Language | GHC2021 |
Polytropos
Description
High-level, consumer-facing API for polytropos: row-polymorphic open records
and open variants for Haskell.
This module is the primary entry point for application developers and library
authors. It encapsulates low-level representation primitives (such as
SmallArray and ) behind ergonomic,
human-friendly type aliases, constructors, and pattern synonyms.Rep
Architectural Overview & Rationale
Conceptual Primer: What Are Row-Polymorphic Open ADTs?
For developers without a background in type theory, the terminology can seem daunting. In practice, it directly solves the rigidity of traditional data types:
- Algebraic Data Types (ADTs): The two universal building blocks of typed data—Product types (structsrecords where data has field A and field B) and Sum types (enumsvariants where data is constructor A or constructor B).
- Closed vs. Open: Traditional Haskell data types are closed—declared once with a fixed set
of fields or constructors. Adding a field (like an authentication token to a
User) or adding a variant branch (like a new AST error node) requires defining a brand-new type and writing tedious conversion boilerplate. Open types can be constructed, extended, and narrowed on the fly without intermediate wrapper types. - Row Polymorphism: A "row" is an unordered collection of named fields (like database columns).
Row polymorphism allows writing functions that operate on any record containing at least
certain fields (or any variant handling at least certain cases), while the compiler statically tracks
and preserves all the other remaining fields (
r). Unlike OOP inheritance, no fields are sliced or erased, and no runtime downcasts are needed. - The Polytropos Difference: Historically, row polymorphism in Haskell was either painfully slow
to compile (quadratic \(O(N^{2.8})\) typeclass search) or memory-inefficient (nested
Eitherpointer trees).polytropossolves this by collapsing products and sums into a single flat runtime representation (a tag plus a contiguousSmallArray) and using a GHC typechecker plugin (polytropos-plugin) to resolve field indices into literal numbers at compile time in \(O(1)\).
The Four-Quadrant Design Space Collapse
Traditional Haskell architectures treat records (products) and variants (sums) as entirely distinct mechanisms, further bifurcated into nominal and structural approaches:
- Nominal Products: Standard Haskell
datarecords (e.g.data Person = Person { name :: String, age :: Int }) - Structural Products: Anonymous extensible records (e.g.
Record '["name" ':= String, "age" ':= Int]) - Nominal Sums: Multi-constructor ADTs (e.g.
data Shape = Circle Double | Rectangle Double Double) - Structural Sums: Extensible variants (e.g.
Variant '[Circle ':= '["radius" ':= Double], ...])
polytropos collapses all four quadrants into one uniform generic representation:
a runtime integer tag plus a contiguous, flat SmallArray of
payload slots (). For a product, the tag is always Rep0 and known
statically. For a sum, the tag is the constructor index.
Heritage & Precedent
The row typing discipline in polytropos draws inspiration from OCaml's polymorphic
variants and extensible rows (Garrigue 1998), adapted to modern GHC with first-class
type-level symbol labels (":=").
The Typechecker Plugin
Rather than paying for \(O(N)\) or \(O(N^2)\) type-level search at compile time or
traversing binary trees at runtime, polytropos delegates evidence resolution to
polytropos-plugin. The plugin computes literal zero-based slot indices, tags, and
dictionary vectors directly in Core AST during compilation, giving \(O(1)\) runtime
indexing with zero runtime overhead.
When to Use This Module
- Use this module (
Polytropos) for almost all daily programming with extensible records, open sums, effect carrier types, and dynamic configuration schemas. - Use satellite packages for domain-specific integrations:
- Polytropos.Generics: whole-data structural equality, comparisons, and generic map/fold
- Polytropos.Optics, Polytropos.Lens, or Polytropos.Microlens: optics and labels
- Polytropos.Aeson: JSON serialization
- Polytropos.CBOR: binary CBOR serialization
- Polytropos.Cassava: CSV serialization
- Polytropos.QuickCheck: property test generators and shrinkers
- Polytropos.Hasql, Polytropos.Rel8, Polytropos.Beam, Polytropos.Persistent: database storage
When Not to Use This Module
- Do not use this module if you are developing custom typechecker plugins, primitive runtime memory layouts, or low-level Core synthesizers; import Polytropos.Core instead.
Relationship to Ecosystem Prior Art
polytropos builds upon extensive historical explorations of extensible records,
anonymous products, and open variants in Haskell:
vinyl: Pioneered type-level list indexing for extensible records and universes, proving the value of structural data while wrestling with superlinear compile-time scaling (\(O(N^{2.8})\)) and deeply abstract type machinery.superrecord: Introduced compile-time field offset computation and compact array representations, demonstrating the performance benefits of avoiding nested pointer trees.row-types: Established a formal row calculus based on Leijen's work with first-class type equality, though encountering stuck type families during type inference failure.extensible: Offered a comprehensive framework unifying records, variants, and effects under custom type-level combinators.open-adt&world-peace: Explored open coproducts and tagged variant unions (OpenUnion), aiming to avoid nestedEithertrees.
polytropos acknowledges these libraries as valuable predecessors striving toward similar goals.
It advances their work by collapsing products and sums into a single flat representation
(SmallArray with an unboxed tag) and delegating all symbol-to-slot
resolution to a dedicated GHC typechecker plugin (polytropos-plugin), achieving \(O(1)\) runtime
indexing, linear \(O(N)\) compile times, and human-readable compiler error diagnostics.
Worked Examples
1. Open Records (Structural Products)
With the polytropos-plugin enabled in your project, open records are constructed,
accessed, and functionally updated using native record syntax desugared at compile time:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedRecordDot #-}
import Polytropos
-- 1a. Idiomatic construction using REC syntax:
myRec :: Record '["x" ':= Int, "y" ':= Bool]
myRec = REC { x = 10, y = True }
-- 1b. Reading fields via OverloadedRecordDot (or getField):
valX :: Int
valX = myRec.x -- 10
-- 1c. Functional record update using REC_UPD (or setField):
myRec' :: Record '["x" ':= Int, "y" ':= Bool]
myRec' = (REC_UPD myRec) { x = 42 }
-- 1d. Record pattern matching with field punning:
formatRec :: Record '["x" ':= Int, "y" ':= Bool] -> String
formatRec (REC { x, y }) = "x=" ++ show x ++ ", y=" ++ show y
-- 1e. Underlying combinator fallback (usable without plugins):
myRecFallback :: Record '["x" ':= Int, "y" ':= Bool]
myRecFallback = insertField (Field @"x") (10 :: Int)
$ insertField (Field @"y") True
$ emptyRecord2. Open Variants (Structural Sums)
Extensible variants represent open coproducts and are injected and pattern matched
using idiomatic VAR syntax:
{-# LANGUAGE DataKinds #-}
import Polytropos
type Event = Variant '["Click" ':= '["x" ':= Int, "y" ':= Int], "Quit" ':= '[]]
-- 2a. Injecting into an open variant using VAR syntax:
clickEvent :: Event
clickEvent = VAR { Click = REC { x = 100, y = 200 } }
quitEvent :: Event
quitEvent = VAR { Quit = emptyRecord }
-- 2b. Pattern matching with VAR view patterns:
handleEvent :: Event -> String
handleEvent (VAR { Click = REC { x, y } }) =
"Clicked at: " ++ show x ++ "," ++ show y
handleEvent (VAR { Quit = _ }) =
"Exiting application"
-- 2c. Underlying combinator fallback:
clickEventFallback :: Event
clickEventFallback = injectVariant (Field @"Click") (insertField (Field @"x") 100 (insertField (Field @"y") 200 emptyRecord))3. Higher-Kinded Records (HKD)
Polytropos provides first-class support for functorial / Higher-Kinded Data rows,
where every field type a is parameterized by an outer type constructor f a
(e.g. Maybe for partial forms or Identity for validated domain entities):
{-# LANGUAGE DataKinds #-}
import Polytropos
type PartialConfig = RecordF Maybe '["host" ':= String, "port" ':= Int]
-- 3a. Constructing HKD records using REC_F:
configTemplate :: PartialConfig
configTemplate = REC_F { host = Just "localhost", port = Nothing }
-- 3b. Functional updates on HKD records using REC_UPD_F:
activeConfig :: PartialConfig
activeConfig = (REC_UPD_F configTemplate) { port = Just 8080 }
-- 3c. Underlying HKD combinator fallback:
configFallback :: PartialConfig
configFallback = insertFieldF (Field @"host") (Just "localhost")
$ insertFieldF (Field @"port") Nothing
$ emptyRecordFSynopsis
- type Record (r :: Row) = RowRep Identity 'ProductRowT r ()
- type RecordF (f :: Type -> Type) (r :: ProductRow f) = RowRep f 'ProductRowHKDT r ()
- type Variant (s :: SumRow) = RowRep Identity 'SumRowT s ()
- type VariantF (f :: Type -> Type) (s :: SumRowHKD f) = RowRep f 'SumRowHKDT s ()
- type Row = RowOf Type
- type SumRow = SumRowOf Row
- type ProductRow (f :: Type -> Type) = RowOf Type
- pattern Variant :: forall (n :: Symbol) s. ConstructorIndex n s => Field n -> Record (ConstructorPayload n s) -> Variant s
- pattern VariantF :: forall (n :: Symbol) f s. ConstructorIndex n s => Field n -> RecordF f (ConstructorPayloadHKD f n s) -> VariantF f s
- emptyRecord :: Record ('[] :: [Pair Symbol Type])
- emptyRecordF :: forall (f :: Type -> Type). RecordF f ('[] :: [Pair Symbol Type])
- insertField :: forall (n :: Symbol) a (r :: Row). Field n -> a -> Record r -> Record ((n ':= a) ': r)
- insertFieldF :: forall (n :: Symbol) f a (r :: ProductRow f). Field n -> f a -> RecordF f r -> RecordF f ((n ':= f a) ': r)
- injectVariant :: forall (n :: Symbol) (s :: SumRow) (fs :: Row). ConstructorIndex n s => Field n -> Record fs -> Variant s
- injectVariantF :: forall (n :: Symbol) (f :: Type -> Type) (s :: SumRowHKD f) (fs :: ProductRow f). ConstructorIndex n s => Field n -> RecordF f fs -> VariantF f s
- projectVariant :: forall (n :: Symbol) (s :: SumRow). ConstructorIndex n s => Field n -> Variant s -> Maybe (Record (ConstructorPayload n s))
- projectVariantF :: forall (n :: Symbol) (f :: Type -> Type) (s :: SumRowHKD f). ConstructorIndex n s => Field n -> VariantF f s -> Maybe (RecordF f (ConstructorPayloadHKD f n s))
- type family ConstructorPayload (n :: Symbol) (s :: SumRow) :: Row where ...
- type family ConstructorPayloadHKD (f :: Type -> Type) (n :: Symbol) (s :: SumRowHKD f) :: ProductRow f where ...
- class SubRow (sub :: k1) (super :: k2) where
- subRowIndices :: [Int]
- class SubSum (sub :: k1) (super :: k2) where
- subSumInjectIndices :: [Word]
- subSumNarrowIndices :: [Maybe Word]
- projectRecord :: forall (sub :: Row) (super :: Row). SubRow sub super => Record super -> Record sub
- projectRecordF :: forall (f :: Type -> Type) (sub :: ProductRow f) (super :: ProductRow f). SubRow sub super => RecordF f super -> RecordF f sub
- widenVariant :: forall (sub :: SumRow) (super :: SumRow). SubSum sub super => Variant sub -> Variant super
- widenVariantF :: forall (f :: Type -> Type) (sub :: SumRowHKD f) (super :: SumRowHKD f). SubSum sub super => VariantF f sub -> VariantF f super
- narrowVariant :: forall (sub :: SumRow) (super :: SumRow). SubSum sub super => Variant super -> Maybe (Variant sub)
- narrowVariantF :: forall (f :: Type -> Type) (sub :: SumRowHKD f) (super :: SumRowHKD f). SubSum sub super => VariantF f super -> Maybe (VariantF f sub)
- peel :: forall (n :: Symbol) (s :: SumRow). ConstructorIndex n s => Variant s -> Either (Variant (Delete n s)) (Record (ConstructorPayload n s))
- peelF :: forall (n :: Symbol) (f :: Type -> Type) (s :: SumRowHKD f). ConstructorIndex n s => VariantF f s -> Either (VariantF f (Delete n s)) (RecordF f (ConstructorPayloadHKD f n s))
- type family Delete (n :: Symbol) (s :: [Pair Symbol k]) :: [Pair Symbol k] where ...
- type family HandlerRecordRow (s :: SumRow) res :: Row where ...
- type family HandlerRecordRowM (s :: SumRow) (m :: Type -> Type) res :: Row where ...
- type family HandlerRecordRowHetero (s :: SumRow) (resRow :: SumRow) :: Row where ...
- type family HandlerRecordRowHeteroM (s :: SumRow) (resRow :: SumRow) (m :: Type -> Type) :: Row where ...
- eliminateUniform :: forall (s :: SumRow) a. Record (HandlerRecordRow s a) -> Variant s -> a
- eliminateUniformM :: forall (s :: SumRow) m a. Record (HandlerRecordRowM s m a) -> Variant s -> m a
- eliminate :: forall (s :: SumRow) (resRow :: SumRow). Record (HandlerRecordRowHetero s resRow) -> Variant s -> Variant resRow
- eliminateM :: forall (s :: SumRow) (resRow :: SumRow) m. Monad m => Record (HandlerRecordRowHeteroM s resRow m) -> Variant s -> m (Variant resRow)
- matchVariant :: forall (s :: SumRow) a. Variant s -> Record (HandlerRecordRow s a) -> a
- data Pair a b = a := b
- class HasField (x :: k) (r :: TYPE r_rep) (a :: TYPE a_rep) | x r -> a where
- getField :: r -> a
- class RowFlavour r => RowEq (r :: RowType) (row :: k) where
- class RowFlavour r => RowShow (r :: RowType) (row :: k) where
- repShowsPrec :: forall {k1} (f :: Type -> Type) (a :: k1). Int -> RowRep f r row a -> ShowS
- class RowFlavour (r :: RowType) where
- data Field (n :: Symbol) = Field
- class RowFlavour r => AllFields (r :: RowType) (row :: k) (c :: Type -> Constraint) where
- fieldDicts :: DictAllFields c
- type DictAllFields (c :: Type -> Constraint) = SmallArray (DictAny c)
- type family FieldNames (r :: k) :: [Symbol]
- type family SimpleFieldTypes (r :: k) :: [(Symbol, Type)]
- type family FieldTypes (f :: Type -> Type) (r :: k) :: [(Symbol, Type)]
- class KnownHash (s :: Symbol) where
- knownHash :: Word
- labelHash :: forall (s :: Symbol) proxy. KnownHash s => proxy s -> Word
- hashVal :: forall (s :: Symbol) proxy. KnownHash s => proxy s -> Word
- fnv1a64 :: String -> Word
- data REC = REC
- data REC_F = REC_F
- data VAR = VAR
- type VARIANT = VAR
- pattern VARIANT :: VAR
- data REC_UPD = REC_UPD
- data REC_UPD_F = REC_UPD_F
- class OpenRecord (a :: k)
- class OpenVariant (a :: k)
- recordField :: forall (n :: Symbol) a (r :: Row). HasField n (Record r) (Identity a) => Field n -> Record r -> a
- setField :: forall (n :: Symbol) a (r :: Row). FieldIndex n r a => Field n -> a -> Record r -> Record r
- setFieldF :: forall (n :: Symbol) a f (r :: ProductRow f). FieldIndex n r (f a) => Field n -> f a -> RecordF f r -> RecordF f r
- cmapRecord :: forall c (r :: Row). AllFields 'ProductRowT r c => (forall x. c x => x -> x) -> Record r -> Record r
- cfoldMapRecord :: forall c (r :: Row) m. (AllFields 'ProductRowT r c, Monoid m) => (forall x. c x => x -> m) -> Record r -> m
- czipWithRecord :: forall c (r :: Row). AllFields 'ProductRowT r c => (forall x. c x => x -> x -> x) -> Record r -> Record r -> Record r
- cmapRecordF :: forall c (f :: Type -> Type) (r :: ProductRow f). AllFields 'ProductRowHKDT r c => (forall x. c x => x -> x) -> RecordF f r -> RecordF f r
- cfoldMapRecordF :: forall c (f :: Type -> Type) (r :: ProductRow f) m. (AllFields 'ProductRowHKDT r c, Monoid m) => (forall x. c x => x -> m) -> RecordF f r -> m
Ergonomic Types & Patterns
type Record (r :: Row) = RowRep Identity 'ProductRowT r () Source #
An open record type, parametrised by a row r with bare field values (f ~ Identity).
- ref:note-consumer-facade
- [ref:note-rep-memory-physics]
A
Recordis a flat, contiguous array of fields. Its memory layout is a single pointer to a small array of payload slots, plus an erased constructor tag (which is always 0 for products).
type RecordF (f :: Type -> Type) (r :: ProductRow f) = RowRep f 'ProductRowHKDT r () Source #
An open record type, parametrised by a functor f and a row r.
- ref:note-consumer-facade
- [ref:note-rep-memory-physics]
A
RecordFis a flat, contiguous array of fields where each field slot contains a value wrapped inf.
type Variant (s :: SumRow) = RowRep Identity 'SumRowT s () Source #
An open variant type, parametrised by a sum row s with bare field values (f ~ Identity).
type VariantF (f :: Type -> Type) (s :: SumRowHKD f) = RowRep f 'SumRowHKDT s () Source #
An open variant type, parametrised by a functor f and a sum row s.
- ref:note-consumer-facade
- [ref:note-rep-memory-physics]
A
VariantFis a single value that can be any one of the constructors defined in the sum rows, with each field slot wrapped inf.
Row Vocabulary & Types
type Row = RowOf Type Source #
A product row: an ordered list of field names and field types.
- ref:note-rep-memory-physics
- Order is the identity of the row; two rows with the same fields in a
different order are different rows. This is deliberate — it is what lets a
field index be a compile-time constant (0, 1, 2, ...) synthesized directly
into Core by the
polytropos-pluginwithout runtime linear scanning.
type SumRow = SumRowOf Row Source #
A sum row: an ordered list of constructor names, each carrying the product row of that constructor's fields.
- ref:note-rep-memory-physics
- The tag of a constructor is its 0-based position in this list. Constructor arities are therefore the lengths of the nested product rows, and may differ (see note §3, D7).
type ProductRow (f :: Type -> Type) = RowOf Type Source #
An HKD-aware product row parameterised by a field functor f.
In an HKD-aware product row, each field value is wrapped in the functor f.
Since f ty has kind Type for any ty :: Type, this is an alias for
a row where field types are parameterised by f.
pattern Variant :: forall (n :: Symbol) s. ConstructorIndex n s => Field n -> Record (ConstructorPayload n s) -> Variant s Source #
A pattern synonym for matching and constructing variants safely.
- ref:note-patterns-view-patterns
pattern VariantF :: forall (n :: Symbol) f s. ConstructorIndex n s => Field n -> RecordF f (ConstructorPayloadHKD f n s) -> VariantF f s Source #
Pattern synonym for matching and constructing HKD variants safely.
- ref:note-patterns-view-patterns
Construction
emptyRecord :: Record ('[] :: [Pair Symbol Type]) Source #
Construct an empty record.
The empty record has no fields and an arity of 0.
emptyRecordF :: forall (f :: Type -> Type). RecordF f ('[] :: [Pair Symbol Type]) Source #
Construct an empty HKD record.
insertField :: forall (n :: Symbol) a (r :: Row). Field n -> a -> Record r -> Record ((n ':= a) ': r) Source #
Insert a new field at the beginning of a record.
This extends the record's row type by prepending the new field. At runtime, this copies the underlying array and adds the new value.
Invariant: This function expects the type-level field list to match the runtime representation. Using it sequentially correctly builds up a record in standard layout format.
insertFieldF :: forall (n :: Symbol) f a (r :: ProductRow f). Field n -> f a -> RecordF f r -> RecordF f ((n ':= f a) ': r) Source #
Insert a new field wrapped in f into an HKD record.
injectVariant :: forall (n :: Symbol) (s :: SumRow) (fs :: Row). ConstructorIndex n s => Field n -> Record fs -> Variant s Source #
Inject a record of fields into a variant for a specific constructor.
injectVariantF :: forall (n :: Symbol) (f :: Type -> Type) (s :: SumRowHKD f) (fs :: ProductRow f). ConstructorIndex n s => Field n -> RecordF f fs -> VariantF f s Source #
Inject an HKD record into an HKD variant for a specific constructor.
projectVariant :: forall (n :: Symbol) (s :: SumRow). ConstructorIndex n s => Field n -> Variant s -> Maybe (Record (ConstructorPayload n s)) Source #
Project a specific constructor from a variant.
projectVariantF :: forall (n :: Symbol) (f :: Type -> Type) (s :: SumRowHKD f). ConstructorIndex n s => Field n -> VariantF f s -> Maybe (RecordF f (ConstructorPayloadHKD f n s)) Source #
Project an HKD record from an HKD variant.
type family ConstructorPayload (n :: Symbol) (s :: SumRow) :: Row where ... Source #
Compute the payload product row of a constructor n in a sum row s.
Equations
| ConstructorPayload n ((n ':= fs) ': rest) = fs | |
| ConstructorPayload n ((m ':= fs) ': rest) = ConstructorPayload n rest |
type family ConstructorPayloadHKD (f :: Type -> Type) (n :: Symbol) (s :: SumRowHKD f) :: ProductRow f where ... Source #
Compute the HKD payload product row of a constructor n in an HKD sum row s.
Equations
| ConstructorPayloadHKD f n ((n ':= fs) ': rest :: [Pair Symbol (ProductRow f)]) = fs | |
| ConstructorPayloadHKD f n ((m ':= fs) ': rest :: [Pair Symbol (ProductRow f)]) = ConstructorPayloadHKD f n rest |
Sub-row & Sub-variant operations
class SubRow (sub :: k1) (super :: k2) where Source #
Sub-row relationship between two product rows.
- ref:note-subrow-subsum
- [ref:note-subrow-subsum-mechanics]
holds when every field inSubRowsub supersuboccurs insuperwith the same field type. The plugin emits the 0-based projection slot indices as evidence.
Algorithmic Mechanics:
For sub-record projection `projectRecord sub superRec`, the runtime must extract
the subset of fields in sub from super. The subRowIndices method returns a
precomputed permutation vector `[Int]` containing the exact slot indices in super@.
This allows projection to execute in a single O(|sub|) pass with zero type search.
Minimal complete definition
Nothing
class SubSum (sub :: k1) (super :: k2) where Source #
Sub-sum relationship between two sum rows (variant widening & narrowing).
- ref:note-subrow-subsum
- [ref:note-subrow-subsum-mechanics]
holds when every constructor inSubSumsub supersuboccurs insuperwith identical field rows. The plugin emits the injection tag indices (for widening) and narrowing tag indices (for projection) as evidence.
Algorithmic Mechanics:
1. Widening (widenVariant):
Total and O(1). Every constructor in sub is present in super. The runtime tag
t in sub is mapped to `subSumInjectIndices !! fromIntegral t`, which is
precomputed by the plugin.
2. Narrowing (narrowVariant):
Partial and O(1). Inspects `subSumNarrowIndices !! fromIntegral t`. If the constructor
is present in sub, returns `Just subTag`; otherwise Nothing.
Minimal complete definition
Nothing
Methods
subSumInjectIndices :: [Word] Source #
For each constructor in sub, its tag in super (widening).
subSumNarrowIndices :: [Maybe Word] Source #
For each constructor in super, its tag in sub if present, or Nothing
if it does not belong to sub (narrowing).
projectRecord :: forall (sub :: Row) (super :: Row). SubRow sub super => Record super -> Record sub Source #
Project a sub-record from a super-record (bare).
Extracts only the fields present in sub in the order specified by sub.
- ref:note-subtyping-combinators
- [ref:note-subrow-subsum] [ref:note-subrow-subsum-mechanics]
Complexity: \(\mathcal{O}(|sub|)\) array copy, with literal projection indices
precomputed at compile-time by the
Polytropos.Plugin.
let fullRec = REC { id = 1 :: Int, name = Alice, role = Admin }
subRec = projectRecord @'["name" ':= String] fullRec
projectRecordF :: forall (f :: Type -> Type) (sub :: ProductRow f) (super :: ProductRow f). SubRow sub super => RecordF f super -> RecordF f sub Source #
Project a sub-record from a super-record (HKD).
Extracts only the fields present in sub in the order specified by sub.
- ref:note-subtyping-combinators
- [ref:note-subrow-subsum] [ref:note-subrow-subsum-mechanics]
Algorithmic Mechanics:
The
TcPluginstatically proves thatsubis a subset ofsuperand emits the projection permutation vectorsubRowIndicessubsuper. At runtime, this function reads each slot from the source array and constructs a new flat array of size|sub|in a single linear pass.
widenVariant :: forall (sub :: SumRow) (super :: SumRow). SubSum sub super => Variant sub -> Variant super Source #
Widen a variant from a sub-row to a super-row (bare).
- ref:note-subtyping-combinators
- [ref:note-subrow-subsum] [ref:note-subrow-subsum-mechanics]
Widening is total and constant time (\(\mathcal{O}(1)\)): the active constructor
tag in
subis remapped to its corresponding tag insupervia a compile-time synthesized literal tag lookup table, requiring zero payload array allocations.
let smallVar = VAR { Ok = REC { val = 200 :: Int } } :: Variant '[Ok ':= '["val" ':= Int]]
wideVar = widenVariant @'[Ok ':= '["val" ':= Int], Err ':= '["msg" ':= String]] smallVar
widenVariantF :: forall (f :: Type -> Type) (sub :: SumRowHKD f) (super :: SumRowHKD f). SubSum sub super => VariantF f sub -> VariantF f super Source #
Widen a variant from a sub-row to a super-row (HKD).
- ref:note-subtyping-combinators
- [ref:note-subrow-subsum] [ref:note-subrow-subsum-mechanics]
Widening is total and constant time: the active constructor tag in
subis remapped to its corresponding tag insuper, preserving payload slots.
narrowVariant :: forall (sub :: SumRow) (super :: SumRow). SubSum sub super => Variant super -> Maybe (Variant sub) Source #
Narrow a variant from a super-row to a sub-row (bare).
- ref:note-subtyping-combinators
- [ref:note-subrow-subsum] [ref:note-subrow-subsum-mechanics]
Returns
Justwith the active tag remapped tosubif the active constructor belongs tosub, orNothingif the constructor is not a member ofsub.
Complexity: \(\mathcal{O}(1)\) time. Reuses the underlying payload array directly.
narrowVariantF :: forall (f :: Type -> Type) (sub :: SumRowHKD f) (super :: SumRowHKD f). SubSum sub super => VariantF f super -> Maybe (VariantF f sub) Source #
Narrow a variant from a super-row to a sub-row (HKD).
- ref:note-subtyping-combinators
- [ref:note-subrow-subsum] [ref:note-subrow-subsum-mechanics]
Returns
Justwith the active tag remapped tosubif the constructor belongs tosub, orNothingif the constructor is not insub.
Elimination & Peeling
peel :: forall (n :: Symbol) (s :: SumRow). ConstructorIndex n s => Variant s -> Either (Variant (Delete n s)) (Record (ConstructorPayload n s)) Source #
Peel a single constructor n from an open variant in \(\mathcal{O}(1)\) time.
- ref:note-peeling-elimination
- If the variant's active constructor is
n, returnsRight (Record fs). Otherwise, returnsLeft (Variant (Delete n s))with the runtime tag decremented if \(t > target\) and preserved if \(t < target\), requiring zero heap reallocation or payload copying.
This is the core primitive enabling single-construct compiler passes (Nanopass):
desugarStmt :: Variant (While ':= fs ': CoreRow) -> Variant CoreRow desugarStmt v = case peel @While v of Right (REC { cond, body }) -> desugarWhile cond body Left rest -> rest -- O(1) pass-through for all other constructs!
peelF :: forall (n :: Symbol) (f :: Type -> Type) (s :: SumRowHKD f). ConstructorIndex n s => VariantF f s -> Either (VariantF f (Delete n s)) (RecordF f (ConstructorPayloadHKD f n s)) Source #
Peel a constructor n from an open HKD variant in \(\mathcal{O}(1)\) time.
- ref:note-peeling-elimination
type family Delete (n :: Symbol) (s :: [Pair Symbol k]) :: [Pair Symbol k] where ... Source #
Delete a constructor n from a sum row s.
Type-level subtraction for open variant rows, enabling type refinement during sequential peeling passes.
type family HandlerRecordRow (s :: SumRow) res :: Row where ... Source #
Row of handler functions for a sum row s returning a uniform result res.
Equations
| HandlerRecordRow ('[] :: [Pair Symbol Row]) _1 = '[] :: [Pair Symbol Type] | |
| HandlerRecordRow ((n ':= fs) ': rest) res = (n ':= (Record fs -> res)) ': HandlerRecordRow rest res |
type family HandlerRecordRowM (s :: SumRow) (m :: Type -> Type) res :: Row where ... Source #
Row of monadic handler functions for a sum row s returning m res.
Equations
| HandlerRecordRowM ('[] :: [Pair Symbol Row]) _1 _2 = '[] :: [Pair Symbol Type] | |
| HandlerRecordRowM ((n ':= fs) ': rest) m res = (n ':= (Record fs -> m res)) ': HandlerRecordRowM rest m res |
type family HandlerRecordRowHetero (s :: SumRow) (resRow :: SumRow) :: Row where ... Source #
Row of heterogeneous handler functions mapping constructors in s to results in resRow.
Equations
| HandlerRecordRowHetero ('[] :: [Pair Symbol Row]) _1 = '[] :: [Pair Symbol Type] | |
| HandlerRecordRowHetero ((n ':= fs) ': rest) ((n ':= rfs) ': rrest) = (n ':= (Record fs -> Record rfs)) ': HandlerRecordRowHetero rest rrest |
type family HandlerRecordRowHeteroM (s :: SumRow) (resRow :: SumRow) (m :: Type -> Type) :: Row where ... Source #
Row of heterogeneous monadic handler functions mapping constructors in s to monadic results in m (Record rfs).
Equations
| HandlerRecordRowHeteroM ('[] :: [Pair Symbol Row]) _1 _2 = '[] :: [Pair Symbol Type] | |
| HandlerRecordRowHeteroM ((n ':= fs) ': rest) ((n ':= rfs) ': rrest) m = (n ':= (Record fs -> m (Record rfs))) ': HandlerRecordRowHeteroM rest rrest m |
eliminateUniform :: forall (s :: SumRow) a. Record (HandlerRecordRow s a) -> Variant s -> a Source #
Eliminate a closed variant uniformly with \(\mathcal{O}(1)\) array jump table dispatch.
- ref:note-array-jump-table-elimination
- Handlers are provided as a
Recordwhere each field corresponds to a constructor ins. The runtime reads the variant's tag \(t\) and directly indexes slot \(t\) of the handler array, executing without linear branch cascades.
let res = eliminateUniform handlers myVariant
where
handlers = REC
{ Ok = \(REC { code, body }) -> "Status " ++ show code
, Err = \(REC { reason }) -> "Error: " ++ reason
}
eliminateUniformM :: forall (s :: SumRow) m a. Record (HandlerRecordRowM s m a) -> Variant s -> m a Source #
Eliminate a closed variant uniformly inside a monad m with \(\mathcal{O}(1)\) array jump table dispatch.
- ref:note-array-jump-table-elimination
eliminate :: forall (s :: SumRow) (resRow :: SumRow). Record (HandlerRecordRowHetero s resRow) -> Variant s -> Variant resRow Source #
Full hybrid eliminator: eliminate a closed variant into an open variant of per-constructor results.
- ref:note-array-jump-table-elimination
- Each handler in
stransforms that constructor's payload into a corresponding result inresRow. The resulting variant retains the original constructor tag and payload in \(\mathcal{O}(1)\) time.
eliminateM :: forall (s :: SumRow) (resRow :: SumRow) m. Monad m => Record (HandlerRecordRowHeteroM s resRow m) -> Variant s -> m (Variant resRow) Source #
Full monadic hybrid eliminator: eliminate a closed variant into an open variant of per-constructor results inside m.
- ref:note-array-jump-table-elimination
matchVariant :: forall (s :: SumRow) a. Variant s -> Record (HandlerRecordRow s a) -> a Source #
Syntactic synonym for eliminateUniform taking the variant value first.
- ref:note-array-jump-table-elimination
- Convenient for direct pattern matching expressions resembling Swift or Rust
match:
matchVariant expr REC
{ lit = \(REC { val }) -> val
, add = \(REC { left, right }) -> left + right
}
Re-exports
A type-level pair: a name bound to a type.
Poly-kinded with independent parameters, mirroring large-anon's
Pair / :=. Kept deliberately simple; the f-functor parameter that
large-anon threads through its rows is future work (see note §1, D3).
Algorithmic Mechanics:
In type expressions, ("name" ':= Type) provides an ergonomic, readable
alternative to tuple pairs '("name", Type). Because := is an ordinary data
constructor promoted by DataKinds, GHC treats it as a first-class type constructor
of kind k1 -> k2 -> Type.
Constructors
| a := b |
class RowFlavour r => RowEq (r :: RowType) (row :: k) where Source #
Field-wise equality for a product row.
The method works over RowRep, so the row is in the type and no Proxy
argument is needed (obligation 4, design note §6). A plugin will build the
body by substituting literal slot indices and a literal element count, so
there is no type-level search at the use site.
- ref:note-literal-evidence
- The test suite writes these bodies by hand, which makes the tests the specification of what the plugin must emit.
Minimal complete definition
Nothing
class RowFlavour r => RowShow (r :: RowType) (row :: k) where Source #
Field-wise Show for a product row. See RowEq for the design.
- ref:note-literal-evidence
- The plugin synthesizes an unrolled sequence of per-field
showsPreccalls, formatting each field label as a literal string"fieldname = "followed by the field value'sshowsPrec, joined by comma separators.
Minimal complete definition
Nothing
Methods
repShowsPrec :: forall {k1} (f :: Type -> Type) (a :: k1). Int -> RowRep f r row a -> ShowS Source #
class RowFlavour (r :: RowType) Source #
The class that abstracts over row flavours.
Each flavour has an associated EntryKind — the kind of a single entry
in a row of that flavour. The plugin uses this to know what to inspect.
Instances
| RowFlavour 'HandlerRowT Source # | A handlerrow entry: a handler type (see | ||||
Defined in Polytropos.Core Associated Types
| |||||
| RowFlavour 'ProductRowHKDT Source # | An HKD-aware productrow entry: a field name paired with an applied functor type. | ||||
Defined in Polytropos.Core Associated Types
| |||||
| RowFlavour 'ProductRowT Source # | A productrow entry: a field name paired with a field type. | ||||
Defined in Polytropos.Core Associated Types
| |||||
| RowFlavour 'ResultRowT Source # | A result-row entry: a result type (see | ||||
Defined in Polytropos.Core Associated Types
| |||||
| RowFlavour 'SumRowHKDT Source # | An HKD-aware sumrow entry: a constructor name paired with an HKD productrow. | ||||
Defined in Polytropos.Core Associated Types
| |||||
| RowFlavour 'SumRowT Source # | A sumrow entry: a constructor name paired with its fieldrow. | ||||
Defined in Polytropos.Core | |||||
data Field (n :: Symbol) Source #
A zero-cost witness for a type-level field name.
Can be constructed ergonomically via OverloadedLabels (e.g. #foo).
Algorithmic Mechanics:
Field has no runtime fields and is represented by GHC as a zero-cost singleton.
When the user writes #foo, GHC desugars this to fromLabel "foo". The
lIsLabel instance below matches when the label string unifies with the
expected field symbol l'@, constructing Field at compile time with zero
runtime allocation.
Constructors
| Field |
class RowFlavour r => AllFields (r :: RowType) (row :: k) (c :: Type -> Constraint) where Source #
Evidence that every field in a row satisfies a class.
This is the generic dictionary-supply class the plugin solves for fully-known rows. Its evidence is a flat array of type-erased field dictionaries, in row order, so callers can map/fold over all fields without writing per-row instances by hand.
The class is kind-polymorphic in the row parameter so the same surface can
be reused for the legacy Row rows and for the HKD-aware product and sum rows
once those are layered on top. The row flavour parameter remains so the
plugin can distinguish product vs sum rows.
Minimal complete definition
Nothing
Methods
fieldDicts :: DictAllFields c Source #
type DictAllFields (c :: Type -> Constraint) = SmallArray (DictAny c) Source #
The evidence type for AllFields: a flat array of type-erased per-field
dictionaries, in row order.
type family FieldNames (r :: k) :: [Symbol] Source #
Extract field names as a type-level list of Symbols.
For each field "name" ':= ty in the row, this evaluates to "name".
When the row is fully known, the TcPlugin rewrites this type family to a
concrete type-level list of symbols.
type family SimpleFieldTypes (r :: k) :: [(Symbol, Type)] Source #
Extract field names and bare field types as a type-level list of pairs.
For each field "name" ':= ty in the row, this evaluates to '("name", ty).
When the row is fully known, the TcPlugin rewrites this type family to a
concrete type-level list of pairs.
type family FieldTypes (f :: Type -> Type) (r :: k) :: [(Symbol, Type)] Source #
Extract field names and instantiated field types as a type-level list of pairs.
For each field "name" ':= ty in the row, this evaluates to '("name", f ty).
When the row is fully known, the TcPlugin rewrites this type family to a
concrete type-level list of pairs.
Label Hashing
class KnownHash (s :: Symbol) where Source #
Class providing a compile-time hash for a label Symbol.
The TcPlugin solves this by evaluating the hash (FNV-1a 64-bit) of the literal
symbol at compile time and emitting a literal Word dictionary.
Purpose and Non-Purpose
- Purpose:
provides O(1) integer keys for dynamic/HAMT label dispatch, label interning, and name-based field lookup without requiringKnownHashKnownSymboldictionary plumbing or runtime string hashing in hot paths.
- Non-purpose:
is not used for slot offset indexing in records and sums. Record slot access inKnownHashpolytroposis statically determined at compile time byFieldIndexandConstructorIndex(0, 1, 2, ...), which are dense, collision-free literal offsets intorepVals.
Algorithmic Mechanics:
The FNV-1a 64-bit algorithm evaluates over the UTF-8 bytes of the label string:
hash = 14695981039346656037 (offset basis)
for each byte c:
hash = (hash xor c) * 1099511628211 (prime)
Minimal complete definition
Nothing
hashVal :: forall (s :: Symbol) proxy. KnownHash s => proxy s -> Word Source #
Synonymous with labelHash for compatibility with large-record conventions.
fnv1a64 :: String -> Word Source #
FNV-1a 64-bit hash of a String, matching the hash computed at compile-time
by the TcPlugin for KnownHash.
Syntactic Devices (Source Plugin)
Syntactic marker for anonymous record construction and pattern matching.
Desugared by the Polytropos source plugin.
- ref:note-source-plugin-arch
Constructors
| REC |
Syntactic marker for anonymous functorial/HKD record construction.
Desugared by the Polytropos source plugin.
- ref:note-source-plugin-arch
Constructors
| REC_F |
Syntactic marker for anonymous variant injection and pattern matching.
Desugared by the Polytropos source plugin.
- ref:note-source-plugin-arch
Constructors
| VAR |
Marker for record update syntax: REC_UPD r { x = 42 }.
Desugared by the Polytropos source plugin into setField calls.
- ref:note-source-plugin-arch
- [ref:note-view-patterns]
Constructors
| REC_UPD |
Marker for functorial/HKD record update syntax: REC_UPD_F r { x = Just 42 }.
Desugared by the Polytropos source plugin into setFieldF calls.
- ref:note-source-plugin-arch
- [ref:note-view-patterns]
Constructors
| REC_UPD_F |
class OpenRecord (a :: k) Source #
Marker class for open record declarations desugared by the source plugin.
- ref:note-open-declarations
class OpenVariant (a :: k) Source #
Marker class for open variant declarations desugared by the source plugin.
- ref:note-open-declarations
recordField :: forall (n :: Symbol) a (r :: Row). HasField n (Record r) (Identity a) => Field n -> Record r -> a Source #
Fetch a field value from a record, used for pattern matching and field access.
Algorithmic Mechanics:
In view patterns emitted by the source plugin for pattern matching on records
(REC { x, y }), field values are extracted using recordField. It queries
GHC's built-in HasField constraint, which is solved by polytropos-plugin
using the precomputed 0-based index of field n.
setField :: forall (n :: Symbol) a (r :: Row). FieldIndex n r a => Field n -> a -> Record r -> Record r Source #
Update an existing field in a Record.
Algorithmic Mechanics:
Resolves FieldIndex at compile time to obtain the literal slot index of n
in r, coerces the new value to 'Identity Any', and delegates to setFieldAt.
setFieldF :: forall (n :: Symbol) a f (r :: ProductRow f). FieldIndex n r (f a) => Field n -> f a -> RecordF f r -> RecordF f r Source #
Update an existing field in a RecordF.
Generic Row Operations (§1.7)
cmapRecord :: forall c (r :: Row). AllFields 'ProductRowT r c => (forall x. c x => x -> x) -> Record r -> Record r Source #
Map a class-constrained function over all fields of a record.
Algorithmic Mechanics:
1. Fetches pre-resolved dictionary vector fieldDicts (a contiguous SmallArray
of type-erased DictAny entries).
2. Clones the underlying payload array into a mutable SmallMutableArray in ST.
3. In a tight unrolled loop from 0 to N-1:
- Fetches the dictionary at slot i and unpacks DictAny, bringing c Any into scope.
- Fetches the payload at slot i, coerces to Any, and invokes f.
- Writes the coerced result back into slot i in-place.
4. Freezes the mutable array into an immutable Record.
Complexity: Exactly 1 array allocation of size N, zero intermediate list allocations.
cfoldMapRecord :: forall c (r :: Row) m. (AllFields 'ProductRowT r c, Monoid m) => (forall x. c x => x -> m) -> Record r -> m Source #
Fold over all fields of a record using a class constraint.
Algorithmic Mechanics:
Recursively traverses the dictionary vector and payload array from index 0 to N-1.
For each slot, unpacks DictAny, maps the value using f to a monoidal value m,
and combines the results using <>.
Complexity: Zero heap array allocations.
czipWithRecord :: forall c (r :: Row). AllFields 'ProductRowT r c => (forall x. c x => x -> x -> x) -> Record r -> Record r -> Record r Source #
Zip two records of identical row shape applying a class-constrained function.
Algorithmic Mechanics:
Walks both records in parallel from slot 0 to N-1, applying the binary function f
using the dictionary from fieldDicts, writing results into a newly allocated array.
Complexity: Exactly 1 array allocation of size N, zero intermediate list allocations.
cmapRecordF :: forall c (f :: Type -> Type) (r :: ProductRow f). AllFields 'ProductRowHKDT r c => (forall x. c x => x -> x) -> RecordF f r -> RecordF f r Source #
Map a class-constrained function over all fields of an HKD record.
cfoldMapRecordF :: forall c (f :: Type -> Type) (r :: ProductRow f) m. (AllFields 'ProductRowHKDT r c, Monoid m) => (forall x. c x => x -> m) -> RecordF f r -> m Source #
Fold over all fields of an HKD record using a class constraint.