| 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.Core
Description
Core vocabulary and generic representation for polytropos.
This module is the foundational substrate of the library. It fixes three
essential architectural components and defers higher-level ergonomics to
Polytropos and the canonical design note (docs/notes/POLYTROPOS_DESIGN.md):
- The row vocabulary: how products and sums are spelled at the type level
(
/Pair,:=,Row,ProductRow).SumRow - The generic representation:
, a runtime tag plus a flat array of payload slots.Rep - The evidence classes:
,RowEq,RowShow,FieldIndex, andConstructorIndex, whose instance bodies are synthesised as literals by the TcPlugin.AllFields
Architectural Overview & Rationale
Why one Rep for both products and sums
A product is a sum with exactly one constructor. Making that literal rather than a special case buys one uniform representation, so the four quadrants of the design space (nominal product, structural product, structural sum of nominal products, nominal sum of structural products) differ only in what the type says, never in what the value is:
data Rep (f :: Type -> Type) a = Rep {-# UNPACK #-} !Word !(SmallArray (f Any))
For a product the tag is always 0 and is known statically, so tag comparisons
fold away completely in GHC Core. For a sum, the tag is the constructor index.
When to Use This Module
- When implementing compilers, typechecker plugins, or low-level primitives.
- When writing custom backend adapters (e.g. database decoders or wire encoders)
that need direct, unboxed access to the underlying
SmallArrayslots.
When Not to Use This Module
- In ordinary application code or domain modeling; prefer the user-friendly primitives and pattern synonyms in Polytropos.
Synopsis
- data Pair a b = a := b
- data Field (n :: Symbol) = Field
- type RowOf k = [Pair Symbol k]
- type Row = RowOf Type
- type ProductRow (f :: Type -> Type) = RowOf Type
- type ProductRowHKD (f :: Type -> Type) = ProductRow f
- type SumRowOf prod = [Pair Symbol prod]
- type SumRow = SumRowOf Row
- type SumRowHKD (f :: Type -> Type) = SumRowOf (ProductRow f)
- type SumRowF (f :: Type -> Type) = SumRowHKD f
- type family FieldNames (r :: k) :: [Symbol]
- type family SimpleFieldTypes (r :: k) :: [(Symbol, Type)]
- type family FieldTypes (f :: Type -> Type) (r :: k) :: [(Symbol, Type)]
- data RowType
- class RowFlavour (r :: RowType) where
- data HandlerType (n :: Symbol) (fs :: Row) res (m :: Type -> Type) = HandlerType
- data ResultType (n :: Symbol) res = ResultType
- data Rep (f :: Type -> Type) (a :: k) = Rep !Word !(SmallArray (f (Any :: Type)))
- repTag :: forall {k} (f :: Type -> Type) (a :: k). Rep f a -> Word
- repVals :: forall {k} f (a :: k). Rep f a -> SmallArray (f (Any :: Type))
- data MRep s (f :: Type -> Type) (a :: k) = MRep !Word !(SmallMutableArray s (f (Any :: Type)))
- thawRep :: forall {k} (f :: Type -> Type) (a :: k) s. Rep f a -> ST s (MRep s f a)
- thawRepSlice :: forall {k} (f :: Type -> Type) (a :: k) s. Rep f a -> Int -> Int -> ST s (MRep s f a)
- freezeRep :: forall {k} s (f :: Type -> Type) (a :: k). MRep s f a -> ST s (Rep f a)
- writeFieldAt :: forall {k} s f (a :: k). MRep s f a -> Int -> f (Any :: Type) -> ST s ()
- writeFieldsAt :: forall {k} s f (a :: k). MRep s f a -> [(Int, f (Any :: Type))] -> ST s ()
- copyFieldsFrom :: forall {k} (f :: Type -> Type) (a :: k) s. Rep f a -> Int -> MRep s f a -> Int -> Int -> ST s ()
- copyMFields :: forall {k} s (f :: Type -> Type) (a :: k). MRep s f a -> Int -> MRep s f a -> Int -> Int -> ST s ()
- newMRep :: forall {k} f s (a :: k). Word -> Int -> f (Any :: Type) -> ST s (MRep s f a)
- modifyRep :: forall {k} (f :: Type -> Type) (a :: k). Rep f a -> (forall s. MRep s f a -> ST s ()) -> Rep f a
- data RowRep (f :: Type -> Type) (r :: RowType) (row :: k1) (a :: k) = RowRep (Rep f a)
- unRowRep :: forall {k1} {k2} (f :: Type -> Type) (r :: RowType) (row :: k1) (a :: k2). RowRep f r row a -> Rep f a
- productRep :: forall {k} f (a :: k). [f (Any :: Type)] -> Rep f a
- injectRep :: forall {k} f (a :: k). Word -> [f (Any :: Type)] -> Rep f a
- repArity :: forall {k} (f :: Type -> Type) (a :: k). Rep f a -> Int
- fieldAt :: forall {k} f (a :: k) x. Int -> Rep f a -> f x
- setFieldAt :: forall {k} f (a :: k). Int -> f (Any :: Type) -> Rep f a -> Rep f a
- setFieldsAt :: forall {k} f (a :: k). [(Int, f (Any :: Type))] -> Rep f a -> Rep f a
- setFieldSumAt :: forall {k} f (a :: k). Word -> Int -> f (Any :: Type) -> Rep f a -> Maybe (Rep f a)
- projectRep :: forall {k} f (a :: k). Word -> Rep f a -> Maybe [f (Any :: Type)]
- matchRep :: (Word -> [f (Any :: Type)] -> a) -> Rep f a -> a
- peelRep :: forall {k} f (a :: k). Word -> Rep f a -> Either (Rep f a) [f (Any :: Type)]
- 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 => RowEqSum (r :: RowType) (row :: k) where
- class RowFlavour r => RowShowSum (r :: RowType) (row :: k) where
- repShowsPrecSum :: forall {k1} (f :: Type -> Type) (a :: k1). Int -> RowRep f r row a -> ShowS
- class FieldIndex (n :: kn) (row :: k) (a :: ka) where
- fieldIndex :: Int
- class ConstructorIndex (n :: kn) (row :: k) where
- constructorIndex :: Word
- data Dict (c :: Type -> Constraint) a where
- data DictAny (c :: Type -> Constraint) where
- mkDictAny :: forall (c :: Type -> Constraint) a. c a => DictAny c
- evidenceAllFields :: forall (c :: Type -> Constraint). [DictAny c] -> DictAllFields c
- class RowFlavour r => AllFields (r :: RowType) (row :: k) (c :: Type -> Constraint) where
- fieldDicts :: DictAllFields c
- type DictAllFields (c :: Type -> Constraint) = SmallArray (DictAny c)
- class SubRow (sub :: k1) (super :: k2) where
- subRowIndices :: [Int]
- class SubSum (sub :: k1) (super :: k2) where
- subSumInjectIndices :: [Word]
- subSumNarrowIndices :: [Maybe Word]
- 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 ProxyRowFlavour (r :: RowType) = ProxyRowFlavour
- type ProductRowProxy = ProxyRowFlavour 'ProductRowT
- type SumRowProxy = ProxyRowFlavour 'SumRowT
- type ProductRowHKDProxy = ProxyRowFlavour 'ProductRowHKDT
- type SumRowHKDProxy = ProxyRowFlavour 'SumRowHKDT
- noInlineUnsafeCo :: a -> b
Rows
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 |
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 |
type RowOf k = [Pair Symbol k] Source #
A poly-kinded row whose entries have kind k.
Poly-kinded over entry kind k, mirroring large-anon's Row k.
A row is represented purely as a standard type-level list [Pair Symbol k].
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 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.
type ProductRowHKD (f :: Type -> Type) = ProductRow f Source #
Synonym for ProductRow.
type SumRowOf prod = [Pair Symbol prod] Source #
A poly-kinded sum row whose constructor payloads have product kind prod.
Sum rows map constructor name symbols to their payload specification.
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 SumRowHKD (f :: Type -> Type) = SumRowOf (ProductRow f) Source #
An HKD-aware sum row parameterised by a field functor f.
Each constructor carries an HKD product row.
Type-level row metadata
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.
Row flavours (Option D vocabulary)
The kind of row flavours (Option D).
Promoted by DataKinds: the types ProductRowT, SumRowT, HandlerRowT,
ResultRowT have kind RowType, and can be used as the row parameter of
RowRep and the evidence classes.
Algorithmic Mechanics:
Option D uses concrete, distinct types for each row flavour rather than an
open kind-polymorphic family. This allows the TcPlugin to immediately
determine whether it is solving for a product row, sum row, HKD variant,
or handler vector by inspecting the promoted type constructor, avoiding
ambiguity and complex kind unification steps during constraint solving.
Constructors
| ProductRowT | field name → field type |
| SumRowT | constructor name → fieldrow |
| HandlerRowT | constructor name → handler type |
| ResultRowT | constructor name → result type |
| ProductRowHKDT | field name → f (field type) |
| SumRowHKDT | constructor name → ProductRow f |
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 | |||||
Handler and result types
data HandlerType (n :: Symbol) (fs :: Row) res (m :: Type -> Type) Source #
A handler type: "handler for constructor n, with field types fs, returning result type res in monad m".
This is the entry kind of HandlerRowT. Each handler entry carries the
constructor name, field types, result type, and monad explicitly, so that
handler rows are self-contained and can be passed around without a paired
sum row (see PHASE2_SKETCH.md, refinement #2).
Constructors
| HandlerType |
data ResultType (n :: Symbol) res Source #
A result type: "result for constructor n, of type res".
This is the entry kind of ResultRowT. Each result entry carries the
constructor name and the result type.
Constructors
| ResultType |
Generic representation
data Rep (f :: Type -> Type) (a :: k) Source #
Generic representation of a product or a sum.
- ref:note-rep-memory-physics
- The layout is exactly the one proposed for this project: a tag plus a flat array of payload slots.
newtype Rep f a = Rep (Word#, SmallArray# (f Any))
written here at the Haskell level, which has the same layout:
{-# UNPACK #-} !Word is Word#, and SmallArray (f Any) is a
SmallArray# (f Any) behind a newtype. SmallArray rather than Vector
because it stores one pointer per element instead of a three-word box, which
is the change large-generics 0.2 made for the same reason.
f is the functor applied to each payload slot, so that Rep I is
isomorphic to the value itself and Rep Maybe boxes the slots.
Constructors
| Rep !Word !(SmallArray (f (Any :: Type))) |
repTag :: forall {k} (f :: Type -> Type) (a :: k). Rep f a -> Word Source #
Constructor index. 0 for a product.
For sums, this identifies which constructor of the sum row is currently active.
repVals :: forall {k} f (a :: k). Rep f a -> SmallArray (f (Any :: Type)) Source #
Payload slots, in row order, of the selected constructor.
For products, this contains all fields of the record. For sums, this contains only the payload fields of the active constructor.
Ephemeral transient representation
data MRep s (f :: Type -> Type) (a :: k) Source #
Ephemeral mutable representation for in-place construction and deserialization.
- ref:note-transient-cow
- Allows O(1) in-place slot modification during multi-field updates, bulk parsing,
or database row decoding without allocating intermediate immutable
SmallArraywrappers.
Constructors
| MRep !Word !(SmallMutableArray s (f (Any :: Type))) |
thawRep :: forall {k} (f :: Type -> Type) (a :: k) s. Rep f a -> ST s (MRep s f a) Source #
Safely thaw an immutable Rep into a mutable builder.
Performs a single heap allocation of a SmallMutableArray and copies the N
pointers from the source array.
thawRepSlice :: forall {k} (f :: Type -> Type) (a :: k) s. Rep f a -> Int -> Int -> ST s (MRep s f a) Source #
Safely thaw a sub-slice of an immutable Rep into a mutable builder.
freezeRep :: forall {k} s (f :: Type -> Type) (a :: k). MRep s f a -> ST s (Rep f a) Source #
Safely freeze an ephemeral builder into an immutable Rep.
Uses unsafeFreezeSmallArray, which is an O(1) zero-copy operation that reuses
the mutable buffer directly as an immutable SmallArray. Safe because caller
relinquishes all mutable references in the enclosing ST action.
writeFieldAt :: forall {k} s f (a :: k). MRep s f a -> Int -> f (Any :: Type) -> ST s () Source #
In-place slot write (O(1), zero allocation).
writeFieldsAt :: forall {k} s f (a :: k). MRep s f a -> [(Int, f (Any :: Type))] -> ST s () Source #
In-place batch slot writes (O(K), zero allocations).
copyFieldsFrom :: forall {k} (f :: Type -> Type) (a :: k) s. Rep f a -> Int -> MRep s f a -> Int -> Int -> ST s () Source #
Batch copy of a slice of fields from an immutable Rep into an MRep.
Uses hardware-accelerated memory block transfer (copySmallArray).
copyMFields :: forall {k} s (f :: Type -> Type) (a :: k). MRep s f a -> Int -> MRep s f a -> Int -> Int -> ST s () Source #
In-place batch block copy between mutable builders.
newMRep :: forall {k} f s (a :: k). Word -> Int -> f (Any :: Type) -> ST s (MRep s f a) Source #
Allocate a new mutable MRep pre-initialized with a default payload.
Used when building a record or variant from scratch in ST before freezing.
modifyRep :: forall {k} (f :: Type -> Type) (a :: k). Rep f a -> (forall s. MRep s f a -> ST s ()) -> Rep f a Source #
Row-carrying wrapper (Case 3)
data RowRep (f :: Type -> Type) (r :: RowType) (row :: k1) (a :: k) Source #
Typed, row-carrying wrapper around Rep.
f is the functor applied to each payload slot.
r is the row flavour (one of ProductRowT, SumRowT, etc.).
row is the actual row data (a type-level list of entries), of any kind.
a is the value type.
At runtime this is exactly Rep f a — a newtype with zero cost.
- ref:note-rep-memory-physics
- Zero-Cost Newtype Erasure:
GHC's Core compiler completely unwraps
RowRepat compile time. At runtime, all operations onRowRepcompile down to direct primitive instructions on the underlyingRep. The type argumentsrandrowact as purely type-level evidence tags, allowing typeclasses likeRowEq,RowShow, andAllFieldsto be invoked directly on records without requiring explicit runtime proxies.
Instances
| RowEq 'ProductRowHKDT row => Eq (RowRep f 'ProductRowHKDT row a) Source # | Standard |
Defined in Polytropos.Core Methods (==) :: RowRep f 'ProductRowHKDT row a -> RowRep f 'ProductRowHKDT row a -> Bool (/=) :: RowRep f 'ProductRowHKDT row a -> RowRep f 'ProductRowHKDT row a -> Bool | |
| RowEq 'ProductRowT row => Eq (RowRep f 'ProductRowT row a) Source # | Standard |
Defined in Polytropos.Core Methods (==) :: RowRep f 'ProductRowT row a -> RowRep f 'ProductRowT row a -> Bool (/=) :: RowRep f 'ProductRowT row a -> RowRep f 'ProductRowT row a -> Bool | |
| RowEqSum 'SumRowHKDT row => Eq (RowRep f 'SumRowHKDT row a) Source # | Standard |
Defined in Polytropos.Core Methods (==) :: RowRep f 'SumRowHKDT row a -> RowRep f 'SumRowHKDT row a -> Bool (/=) :: RowRep f 'SumRowHKDT row a -> RowRep f 'SumRowHKDT row a -> Bool | |
| RowEqSum 'SumRowT row => Eq (RowRep f 'SumRowT row a) Source # | Standard |
| (RowEq 'ProductRowHKDT row, AllFields 'ProductRowHKDT row Ord) => Ord (RowRep f 'ProductRowHKDT row a) Source # | Standard |
Defined in Polytropos.Core Methods compare :: RowRep f 'ProductRowHKDT row a -> RowRep f 'ProductRowHKDT row a -> Ordering (<) :: RowRep f 'ProductRowHKDT row a -> RowRep f 'ProductRowHKDT row a -> Bool (<=) :: RowRep f 'ProductRowHKDT row a -> RowRep f 'ProductRowHKDT row a -> Bool (>) :: RowRep f 'ProductRowHKDT row a -> RowRep f 'ProductRowHKDT row a -> Bool (>=) :: RowRep f 'ProductRowHKDT row a -> RowRep f 'ProductRowHKDT row a -> Bool max :: RowRep f 'ProductRowHKDT row a -> RowRep f 'ProductRowHKDT row a -> RowRep f 'ProductRowHKDT row a min :: RowRep f 'ProductRowHKDT row a -> RowRep f 'ProductRowHKDT row a -> RowRep f 'ProductRowHKDT row a | |
| (RowEq 'ProductRowT row, AllFields 'ProductRowT row Ord) => Ord (RowRep f 'ProductRowT row a) Source # | Standard Algorithmic Mechanics:
Walks the field dictionary array and the two value arrays in lockstep from
index 0 to N-1. For each slot, unpacks |
Defined in Polytropos.Core Methods compare :: RowRep f 'ProductRowT row a -> RowRep f 'ProductRowT row a -> Ordering (<) :: RowRep f 'ProductRowT row a -> RowRep f 'ProductRowT row a -> Bool (<=) :: RowRep f 'ProductRowT row a -> RowRep f 'ProductRowT row a -> Bool (>) :: RowRep f 'ProductRowT row a -> RowRep f 'ProductRowT row a -> Bool (>=) :: RowRep f 'ProductRowT row a -> RowRep f 'ProductRowT row a -> Bool max :: RowRep f 'ProductRowT row a -> RowRep f 'ProductRowT row a -> RowRep f 'ProductRowT row a min :: RowRep f 'ProductRowT row a -> RowRep f 'ProductRowT row a -> RowRep f 'ProductRowT row a | |
| RowShow 'ProductRowHKDT row => Show (RowRep f 'ProductRowHKDT row a) Source # | Standard |
Defined in Polytropos.Core Methods showsPrec :: Int -> RowRep f 'ProductRowHKDT row a -> ShowS show :: RowRep f 'ProductRowHKDT row a -> String showList :: [RowRep f 'ProductRowHKDT row a] -> ShowS | |
| RowShow 'ProductRowT row => Show (RowRep f 'ProductRowT row a) Source # | Standard |
Defined in Polytropos.Core Methods showsPrec :: Int -> RowRep f 'ProductRowT row a -> ShowS show :: RowRep f 'ProductRowT row a -> String showList :: [RowRep f 'ProductRowT row a] -> ShowS | |
| RowShowSum 'SumRowHKDT row => Show (RowRep f 'SumRowHKDT row a) Source # | Standard |
Defined in Polytropos.Core Methods showsPrec :: Int -> RowRep f 'SumRowHKDT row a -> ShowS show :: RowRep f 'SumRowHKDT row a -> String showList :: [RowRep f 'SumRowHKDT row a] -> ShowS | |
| RowShowSum 'SumRowT row => Show (RowRep f 'SumRowT row a) Source # | Standard |
unRowRep :: forall {k1} {k2} (f :: Type -> Type) (r :: RowType) (row :: k1) (a :: k2). RowRep f r row a -> Rep f a Source #
Construction
productRep :: forall {k} f (a :: k). [f (Any :: Type)] -> Rep f a Source #
Build a product from its field values, in row order.
- ref:note-rep-memory-physics
- The tag is
0: a product is the one-constructor sum. The payload values are placed directly into a contiguousSmallArray.
injectRep :: forall {k} f (a :: k). Word -> [f (Any :: Type)] -> Rep f a Source #
Build a sum value carrying the given tag and that constructor's fields.
- ref:note-rep-memory-physics
- The tag
tis an unboxed integer identifying the active constructor. The payload array contains exactly the fields required by constructort.
Access
repArity :: forall {k} (f :: Type -> Type) (a :: k). Rep f a -> Int Source #
How many payload slots the selected constructor has.
fieldAt :: forall {k} f (a :: k) x. Int -> Rep f a -> f x Source #
Read the payload slot at a compile-time-known index.
The x is not determined by the index alone; it comes from the row type. The
coercion is unchecked, exactly as in large-anon: the index is only ever
produced by the plugin from a row that actually has that field.
Algorithmic Mechanics:
In Core, this lowers directly to indexSmallArray# followed by a no-op cast.
Because index i is a compile-time constant emitted by the TcPlugin, there
is zero runtime dictionary lookup, zero branch instructions, and zero heap allocation.
setFieldAt :: forall {k} f (a :: k). Int -> f (Any :: Type) -> Rep f a -> Rep f a Source #
Replace the payload slot at a compile-time-known index.
- ref:note-transient-cow
- For records with
>32fields (Milestone T3), uses hardware-vectorized SIMD non-overlapping block copies (copySmallArray), eliminating redundant reads and writes of slotifrom the source array.
For small records (<=32 fields), uses direct in-cache thawSmallArray.
setFieldsAt :: forall {k} f (a :: k). [(Int, f (Any :: Type))] -> Rep f a -> Rep f a Source #
Clone once, apply K updates in-place, freeze once.
- ref:note-transient-cow
- Reduces multi-field update overhead from
K * O(N)to1 * O(N). For records with>32fields (Milestone T3), sorts update offsets and copies non-overlapping unchanged slices in contiguous vectorized blocks.
setFieldSumAt :: forall {k} f (a :: k). Word -> Int -> f (Any :: Type) -> Rep f a -> Maybe (Rep f a) Source #
Partial field update for sum constructors.
Ensures the runtime tag matches the expected constructor tag before updating.
If the sum value is not currently the specified constructor, returns Nothing.
projectRep :: forall {k} f (a :: k). Word -> Rep f a -> Maybe [f (Any :: Type)] Source #
Project a constructor, or Nothing if the tag does not match.
A sum over a superset of the row can therefore be narrowed, but only
observably: the failure is a Nothing, not a type error. See note §3, D8 for
why widening is total and narrowing is not.
matchRep :: (Word -> [f (Any :: Type)] -> a) -> Rep f a -> a Source #
Eliminate a sum: hand the tag and the selected payload to a continuation.
The continuation sees the tag, so the unmatched case is the continuation's own business; this primitive does not decide it. The elimination story is unsettled — see note §4.
peelRep :: forall {k} f (a :: k). Word -> Rep f a -> Either (Rep f a) [f (Any :: Type)] Source #
Peel a target tag from a sum representation in \(\mathcal{O}(1)\) time.
- ref:note-peeling-elimination
- If
repTag r == targetTag, returnsRight (toListSlots (repVals r)). Otherwise, returnsLeft r'where the remaining tag is preserved ifrepTag r < targetTag, or decremented by 1 ifrepTag r > targetTag.
Complexity: \(\mathcal{O}(1)\) time, requiring zero array payload copying or allocations.
Evidence classes
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 => RowEqSum (r :: RowType) (row :: k) where Source #
Field-wise equality for a sum row.
Sums need their own class because the comparison includes the tag (the constructor index), which products don't have (their tag is always 0 and folds away).
- ref:note-literal-evidence
- Algorithmic Mechanics:
Two variant values
v1andv2are equal iff: 1. Their runtime tags match: `repTag v1 == repTag v2` 2. Their payload fields for that specific active constructor match. The plugin emits a Core `case` expression on the tag, branching into per-constructor literal slot equality comparisons.
Minimal complete definition
Nothing
class RowFlavour r => RowShowSum (r :: RowType) (row :: k) where Source #
Field-wise Show for a sum row.
- ref:note-literal-evidence
- The plugin generates a jump table on
repTag, emitting the constructor name as a string literal followed by its payload record's field representations.
Minimal complete definition
Nothing
Methods
repShowsPrecSum :: forall {k1} (f :: Type -> Type) (a :: k1). Int -> RowRep f r row a -> ShowS Source #
class FieldIndex (n :: kn) (row :: k) (a :: ka) where Source #
Evidence that a field name or type n has a slot at a known index in a row.
The plugin solves this by inspecting the row and emitting the index as a literal (obligation 1, design note §5).
Algorithmic Mechanics:
When GHC encounters a wanted constraint `FieldIndex "foo" r a`, the plugin
scans the type-level list r for the entry `"foo" ':= a`. It returns the
0-based integer offset directly as a Core literal `Lit (MachInt i)`.
Minimal complete definition
Nothing
Methods
fieldIndex :: Int Source #
class ConstructorIndex (n :: kn) (row :: k) where Source #
Evidence that a constructor name or type n has a known tag in a row.
The plugin solves this by inspecting the row and emitting the tag as a literal (obligation 2, design note §5).
Algorithmic Mechanics:
For wanted `ConstructorIndex Bar s`, the plugin locates `Bar` in the sum
row s and produces an unboxed integer literal `Lit (MachWord t)`.
Minimal complete definition
Nothing
Methods
constructorIndex :: Word Source #
data Dict (c :: Type -> Constraint) a where Source #
Witness for a class constraint c a.
Standard GADT representation of an explicit typeclass dictionary.
data DictAny (c :: Type -> Constraint) where Source #
Evidence that all fields of a row satisfy some class.
DictAny c is the type-erased form of c Any, mirroring large-anon's
dictionary packing. It is what lets one dictionary carry many field
dictionaries without a type-level list of them.
Algorithmic Mechanics:
Under the hood, GHC typeclass dictionaries are ordinary record values containing
function pointers (vtable). Because all boxed heap objects have identical runtime
representation (a pointer), a dictionary for `c a` can be coerced to `c Any` via
noInlineUnsafeCo. At point of consumption, passing an Any value cast from a
to a method in `c Any` is completely type-safe because the vtable functions match
the actual runtime type of the value.
mkDictAny :: forall (c :: Type -> Constraint) a. c a => DictAny c Source #
Wrap a class dictionary into DictAny.
evidenceAllFields :: forall (c :: Type -> Constraint). [DictAny c] -> DictAllFields c Source #
Construct evidence for the AllFields class.
Takes a list of type-erased dictionaries and places them into a contiguous
SmallArray for O(1) indexed lookup during generic loops.
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.
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).
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.
data ProxyRowFlavour (r :: RowType) Source #
A stand-in proxy for the row flavour. This is used in the smoke test as a
placeholder for the row-flavour parameter of AllFields until the row
vocabulary is finalised and the class is re-indexed.
Constructors
| ProxyRowFlavour |
Instances
| Show (ProxyRowFlavour r) Source # | |
Defined in Polytropos.Core Methods showsPrec :: Int -> ProxyRowFlavour r -> ShowS show :: ProxyRowFlavour r -> String showList :: [ProxyRowFlavour r] -> ShowS | |
type ProductRowProxy = ProxyRowFlavour 'ProductRowT Source #
The row-flavour proxy for product rows, used in the smoke test.
type SumRowProxy = ProxyRowFlavour 'SumRowT Source #
The row-flavour proxy for sum rows, for use in the smoke test once sum rows are exercised.
type ProductRowHKDProxy = ProxyRowFlavour 'ProductRowHKDT Source #
The row-flavour proxy for HKD product rows.
type SumRowHKDProxy = ProxyRowFlavour 'SumRowHKDT Source #
The row-flavour proxy for HKD sum rows.
Utilities
noInlineUnsafeCo :: a -> b Source #
unsafeCoerce that survives GHC's Core linter.
Must be marked NOINLINE: large-anon found that without it, GHC 9.6
produces -dcore-lint errors for the evidence built on top of this.
Algorithmic Mechanics & Compiler Safety Invariant:
In modern GHC (9.6 through 9.14+), the Core simplifier performs aggressive
inlining and floating of coercions. If an unchecked coercion is marked inlineable,
GHC's simplifier may attempt to float the cast outside of enclosing lambdas or
push it across case branches where the type invariants are not yet established.
When running with -dcore-lint, this causes fatal Core Lint failures.
Marking noInlineUnsafeCo as {-# NOINLINE #-} establishes an impenetrable
optimization barrier, preventing the simplifier from inspecting or hoisting
the underlying unsafeCoerce# primitive while still emitting zero runtime instructions.