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

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 Rep) behind ergonomic, human-friendly type aliases, constructors, and pattern synonyms.

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:

  1. 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).
  2. 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.
  3. 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.
  4. 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 Either pointer trees). polytropos solves this by collapsing products and sums into a single flat runtime representation (a tag plus a contiguous SmallArray) 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:

  1. Nominal Products: Standard Haskell data records (e.g. data Person = Person { name :: String, age :: Int })
  2. Structural Products: Anonymous extensible records (e.g. Record '["name" ':= String, "age" ':= Int])
  3. Nominal Sums: Multi-constructor ADTs (e.g. data Shape = Circle Double | Rectangle Double Double)
  4. 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 (Rep). For a product, the tag is always 0 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

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 nested Either trees.

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
              $ emptyRecord

2. 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
               $ emptyRecordF
Synopsis

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 Record is 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 RecordF is a flat, contiguous array of fields where each field slot contains a value wrapped in f.

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

ref:note-consumer-facade
[ref:note-rep-memory-physics] A Variant is a single value that can be any one of the constructors defined in the sum row s. Its memory layout is exactly identical to a Record, except the tag distinguishes which constructor is currently active.

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 VariantF is a single value that can be any one of the constructors defined in the sum row s, with each field slot wrapped in f.

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-plugin without 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] SubRow sub super holds when every field in sub occurs in super with 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

Methods

subRowIndices :: [Int] Source #

For each field in sub, its 0-based slot index in super.

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] SubSum sub super holds when every constructor in sub occurs in super with 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 TcPlugin statically proves that sub is a subset of super and emits the projection permutation vector subRowIndices sub super. 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 sub is remapped to its corresponding tag in super via 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 sub is remapped to its corresponding tag in super, 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 Just with the active tag remapped to sub if the active constructor belongs to sub, or Nothing if the constructor is not a member of sub.

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 Just with the active tag remapped to sub if the constructor belongs to sub, or Nothing if the constructor is not in sub.

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, returns Right (Record fs). Otherwise, returns Left (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.

Equations

Delete n ((n ':= fs) ': rest :: [Pair Symbol k]) = rest 
Delete n ((m ':= fs) ': rest :: [Pair Symbol k]) = (m ':= fs) ': Delete n rest 
Delete n ('[] :: [Pair Symbol k]) = '[] :: [Pair Symbol k] 

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 Record where each field corresponds to a constructor in s. 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 s transforms that constructor's payload into a corresponding result in resRow. 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

data Pair a b Source #

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 HasField (x :: k) (r :: TYPE r_rep) (a :: TYPE a_rep) | x r -> a where #

Methods

getField :: r -> a #

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

Methods

repEq :: forall {k1} (f :: Type -> Type) (a :: k1). RowRep f r row a -> RowRep f r row a -> Bool Source #

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 showsPrec calls, formatting each field label as a literal string "fieldname = " followed by the field value's showsPrec, 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.

Associated Types

type EntryKind (r :: RowType) Source #

The kind of entries in a row of this flavour.

Instances

Instances details
RowFlavour 'HandlerRowT Source #

A handlerrow entry: a handler type (see HandlerType).

Instance details

Defined in Polytropos.Core

Associated Types

type EntryKind 'HandlerRowT 
Instance details

Defined in Polytropos.Core

type EntryKind 'HandlerRowT = Pair Symbol Type
RowFlavour 'ProductRowHKDT Source #

An HKD-aware productrow entry: a field name paired with an applied functor type.

Instance details

Defined in Polytropos.Core

Associated Types

type EntryKind 'ProductRowHKDT 
Instance details

Defined in Polytropos.Core

type EntryKind 'ProductRowHKDT = Pair Symbol Type
RowFlavour 'ProductRowT Source #

A productrow entry: a field name paired with a field type.

Instance details

Defined in Polytropos.Core

Associated Types

type EntryKind 'ProductRowT 
Instance details

Defined in Polytropos.Core

type EntryKind 'ProductRowT = Pair Symbol Type
RowFlavour 'ResultRowT Source #

A result-row entry: a result type (see ResultType).

Instance details

Defined in Polytropos.Core

Associated Types

type EntryKind 'ResultRowT 
Instance details

Defined in Polytropos.Core

type EntryKind 'ResultRowT = Pair Symbol Type
RowFlavour 'SumRowHKDT Source #

An HKD-aware sumrow entry: a constructor name paired with an HKD productrow.

Instance details

Defined in Polytropos.Core

Associated Types

type EntryKind 'SumRowHKDT 
Instance details

Defined in Polytropos.Core

type EntryKind 'SumRowHKDT = Pair Symbol Row
RowFlavour 'SumRowT Source #

A sumrow entry: a constructor name paired with its fieldrow.

Instance details

Defined in Polytropos.Core

Associated Types

type EntryKind 'SumRowT 
Instance details

Defined in Polytropos.Core

type EntryKind 'SumRowT = Pair Symbol Row

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 IsLabel instance below matches when the label string l unifies with the expected field symbol l'@, constructing Field at compile time with zero runtime allocation.

Constructors

Field 

Instances

Instances details
l ~ l' => IsLabel l (Field l') Source # 
Instance details

Defined in Polytropos.Core

Methods

fromLabel :: Field l'

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

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: KnownHash provides O(1) integer keys for dynamic/HAMT label dispatch, label interning, and name-based field lookup without requiring KnownSymbol dictionary plumbing or runtime string hashing in hot paths.
  • Non-purpose: KnownHash is not used for slot offset indexing in records and sums. Record slot access in polytropos is statically determined at compile time by FieldIndex and ConstructorIndex (0, 1, 2, ...), which are dense, collision-free literal offsets into repVals.

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

Methods

knownHash :: Word Source #

Literal compile-time hash for label s.

labelHash :: forall (s :: Symbol) proxy. KnownHash s => proxy s -> Word Source #

Ergonomic helper to extract the compile-time hash of a label via a proxy (such as Field or Proxy).

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)

data REC Source #

Syntactic marker for anonymous record construction and pattern matching. Desugared by the Polytropos source plugin.

ref:note-source-plugin-arch

Constructors

REC 

Instances

Instances details
Eq REC Source # 
Instance details

Defined in Polytropos

Methods

(==) :: REC -> REC -> Bool

(/=) :: REC -> REC -> Bool

Show REC Source # 
Instance details

Defined in Polytropos

Methods

showsPrec :: Int -> REC -> ShowS

show :: REC -> String

showList :: [REC] -> ShowS

data REC_F Source #

Syntactic marker for anonymous functorial/HKD record construction. Desugared by the Polytropos source plugin.

ref:note-source-plugin-arch

Constructors

REC_F 

Instances

Instances details
Eq REC_F Source # 
Instance details

Defined in Polytropos

Methods

(==) :: REC_F -> REC_F -> Bool

(/=) :: REC_F -> REC_F -> Bool

Show REC_F Source # 
Instance details

Defined in Polytropos

Methods

showsPrec :: Int -> REC_F -> ShowS

show :: REC_F -> String

showList :: [REC_F] -> ShowS

data VAR Source #

Syntactic marker for anonymous variant injection and pattern matching. Desugared by the Polytropos source plugin.

ref:note-source-plugin-arch

Constructors

VAR 

Instances

Instances details
Eq VAR Source # 
Instance details

Defined in Polytropos

Methods

(==) :: VAR -> VAR -> Bool

(/=) :: VAR -> VAR -> Bool

Show VAR Source # 
Instance details

Defined in Polytropos

Methods

showsPrec :: Int -> VAR -> ShowS

show :: VAR -> String

showList :: [VAR] -> ShowS

type VARIANT = VAR Source #

Syntactic alias for VAR.

pattern VARIANT :: VAR Source #

Pattern synonym alias for VAR.

data REC_UPD Source #

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 

Instances

Instances details
Eq REC_UPD Source # 
Instance details

Defined in Polytropos

Methods

(==) :: REC_UPD -> REC_UPD -> Bool

(/=) :: REC_UPD -> REC_UPD -> Bool

Show REC_UPD Source # 
Instance details

Defined in Polytropos

Methods

showsPrec :: Int -> REC_UPD -> ShowS

show :: REC_UPD -> String

showList :: [REC_UPD] -> ShowS

data REC_UPD_F Source #

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 

Instances

Instances details
Eq REC_UPD_F Source # 
Instance details

Defined in Polytropos

Methods

(==) :: REC_UPD_F -> REC_UPD_F -> Bool

(/=) :: REC_UPD_F -> REC_UPD_F -> Bool

Show REC_UPD_F Source # 
Instance details

Defined in Polytropos

Methods

showsPrec :: Int -> REC_UPD_F -> ShowS

show :: REC_UPD_F -> String

showList :: [REC_UPD_F] -> ShowS

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.