| 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.Lens
Description
Complete integration with Edward Kmett's lens library while keeping the
core polytropos packages free of the heavyweight transitive dependency tree.
Architectural Overview & Rationale
This module bridges polytropos flat runtime representations to standard lens
abstractions:
* Positional lenses: Field1 through Field9 instances on product records,
so rec ^. _1 and rec ^. _2 read the first and second slots by position in \(O(1)\).
* Name-based lenses: fieldLens focuses on a field by label symbol with literal
index calculation via polytropos-plugin.
* Container classes: Ixed and At instances keyed by field name for dynamic
or symbol-indexed access.
* Homogeneous traversals: Each instances over records whose fields share one
type (AllFieldsAre).
* Variant prisms: prismVariant constructs first-class Prism' optics for
open variant constructors with \(O(1)\) tag matching.
When to Use This Module
- When your codebase heavily utilizes the full
lenshierarchy (profunctor optics, indexed traversals,Field1..Field9).
When Not to Use This Module
- If you only need lightweight lenses and want to minimize dependencies, use Polytropos.Microlens instead.
- If your codebase uses the newer
opticshierarchy, use Polytropos.Optics instead.
Worked Example
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -fplugin=Polytropos.Plugin #-}
import Control.Lens
import Polytropos
import Polytropos.Lens
-- A genuinely heterogeneous record:
type Person = Record '["name" ':= String, "age" ':= Int]
person :: Person
person = REC { name = "Bob", age = 25 }
-- Positional heterogeneous access:
firstField :: String
firstField = person ^. _1 -- "Bob"
-- Name-based heterogeneous access:
ageVal :: Int
ageVal = person ^. fieldLens (Field @"age") -- 25
-- Positional modification:
olderPerson :: Person
olderPerson = person & _2 .~ 26
-- Name-based modification:
olderPerson' :: Person
olderPerson' = person & fieldLens (Field @"age") %~ (+ 1)Synopsis
- fieldLens :: forall (n :: Symbol) (r :: Row) a. FieldIndex n r a => Field n -> Lens' (Record r) a
- prismVariant :: forall (c :: Symbol) (s :: SumRow) (fs :: Row). ConstructorIndex c s => Field c -> Prism' (Variant s) (Record fs)
- type family AllFieldsAre a (r :: Row) where ...
- type family RowField (r :: Row) where ...
- class FieldNames (r :: Row) where
- fieldNames :: Proxy r -> [String]
Field Lenses by Name
fieldLens :: forall (n :: Symbol) (r :: Row) a. FieldIndex n r a => Field n -> Lens' (Record r) a Source #
Lens on a record field by name.
fieldLens#foo ::FieldIndex"foo" r a =>Lens'(Recordr) a
The FieldIndex dictionary carries the slot index as a literal, computed
by the TcPlugin; there is no runtime name lookup and no type-level search.
Variant Prisms
prismVariant :: forall (c :: Symbol) (s :: SumRow) (fs :: Row). ConstructorIndex c s => Field c -> Prism' (Variant s) (Record fs) Source #
Prism from a sum variant to the field record of one constructor.
prismVariant(Field @"Some") ::ConstructorIndex"Some" s =>Prism'(tVariants) (Recordfs)
Built from injectVariant and projectRep: the
ConstructorIndex dictionary carries the constructor's tag as a literal,
so matching is a single runtime tag comparison.
Homogeneity Evidence and Row Introspection
type family AllFieldsAre a (r :: Row) where ... Source #
Evidence that every field of the row r has type a.
Closed type family, so a heterogeneous row fails to reduce and the dependent instances simply do not apply.
Equations
| AllFieldsAre _1 ('[] :: [Pair Symbol Type]) = () | |
| AllFieldsAre a ((n ':= a) ': rest) = AllFieldsAre a rest |
type family RowField (r :: Row) where ... Source #
The element type of a homogeneous row: the type of every field.
Undefined for '[] (an empty record has no field type) and meaningless for
heterogeneous rows; both cases are ruled out where it matters by
AllFieldsAre.
class FieldNames (r :: Row) where Source #
The field names of a row, in row order, reified from the type level.
Methods
fieldNames :: Proxy r -> [String] Source #
Instances
| FieldNames ('[] :: [Pair Symbol Type]) Source # | |
Defined in Polytropos.Lens Methods fieldNames :: Proxy ('[] :: [Pair Symbol Type]) -> [String] Source # | |
| (KnownSymbol n, FieldNames r) => FieldNames ((n ':= a) ': r) Source # | |
Defined in Polytropos.Lens Methods fieldNames :: Proxy ((n ':= a) ': r) -> [String] Source # | |
Orphan instances
| Ixed (Record r) => At (Record r) Source # |
Map-style lookup and update by field name, defined in terms of |
| (FieldNames r, AllFieldsAre (RowField r) r) => Ixed (Record r) Source # | |
| AllFieldsAre a r => Each (Record ((n ':= a) ': r)) (Record ((n ':= a) ': r)) a a Source # | each :: AllFieldsAre a r => Traversal (Record ((n ':= a) ': r)) (The instance head fixes the element type to the row's field type, which is
what the |
| Field1 (Record ((n ':= a) ': r)) (Record ((n ':= a) ': r)) a a Source # | |
| Field2 (Record ((n1 ':= a1) ': ((n2 ':= a2) ': r))) (Record ((n1 ':= a1) ': ((n2 ':= a2) ': r))) a2 a2 Source # | |
| Field3 (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': r)))) (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': r)))) a3 a3 Source # | |
| Field4 (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': ((n4 ':= a4) ': r))))) (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': ((n4 ':= a4) ': r))))) a4 a4 Source # | |
| Field5 (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': ((n4 ':= a4) ': ((n5 ':= a5) ': r)))))) (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': ((n4 ':= a4) ': ((n5 ':= a5) ': r)))))) a5 a5 Source # | |
| Field6 (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': ((n4 ':= a4) ': ((n5 ':= a5) ': ((n6 ':= a6) ': r))))))) (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': ((n4 ':= a4) ': ((n5 ':= a5) ': ((n6 ':= a6) ': r))))))) a6 a6 Source # | |
| Field7 (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': ((n4 ':= a4) ': ((n5 ':= a5) ': ((n6 ':= a6) ': ((n7 ':= a7) ': r)))))))) (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': ((n4 ':= a4) ': ((n5 ':= a5) ': ((n6 ':= a6) ': ((n7 ':= a7) ': r)))))))) a7 a7 Source # | |
| Field8 (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': ((n4 ':= a4) ': ((n5 ':= a5) ': ((n6 ':= a6) ': ((n7 ':= a7) ': ((n8 ':= a8) ': r))))))))) (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': ((n4 ':= a4) ': ((n5 ':= a5) ': ((n6 ':= a6) ': ((n7 ':= a7) ': ((n8 ':= a8) ': r))))))))) a8 a8 Source # | |
Methods _8 :: Lens (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': ((n4 ':= a4) ': ((n5 ':= a5) ': ((n6 ':= a6) ': ((n7 ':= a7) ': ((n8 ':= a8) ': r))))))))) (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': ((n4 ':= a4) ': ((n5 ':= a5) ': ((n6 ':= a6) ': ((n7 ':= a7) ': ((n8 ':= a8) ': r))))))))) a8 a8 # | |
| Field9 (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': ((n4 ':= a4) ': ((n5 ':= a5) ': ((n6 ':= a6) ': ((n7 ':= a7) ': ((n8 ':= a8) ': ((n9 ':= a9) ': r)))))))))) (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': ((n4 ':= a4) ': ((n5 ':= a5) ': ((n6 ':= a6) ': ((n7 ':= a7) ': ((n8 ':= a8) ': ((n9 ':= a9) ': r)))))))))) a9 a9 Source # | |
Methods _9 :: Lens (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': ((n4 ':= a4) ': ((n5 ':= a5) ': ((n6 ':= a6) ': ((n7 ':= a7) ': ((n8 ':= a8) ': ((n9 ':= a9) ': r)))))))))) (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': ((n4 ':= a4) ': ((n5 ':= a5) ': ((n6 ':= a6) ': ((n7 ':= a7) ': ((n8 ':= a8) ': ((n9 ':= a9) ': r)))))))))) a9 a9 # | |