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

Polytropos.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 lens hierarchy (profunctor optics, indexed traversals, Field1..Field9).

When Not to Use This Module

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

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' (Record r) 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' (tVariant s) (Record fs)

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.

Equations

RowField ('[] :: [Pair Symbol Type]) = Void 
RowField ((n ':= a) ': rest) = a 

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

Instances details
FieldNames ('[] :: [Pair Symbol Type]) Source # 
Instance details

Defined in Polytropos.Lens

Methods

fieldNames :: Proxy ('[] :: [Pair Symbol Type]) -> [String] Source #

(KnownSymbol n, FieldNames r) => FieldNames ((n ':= a) ': r) Source # 
Instance details

Defined in Polytropos.Lens

Methods

fieldNames :: Proxy ((n ':= a) ': r) -> [String] Source #

Orphan instances

Ixed (Record r) => At (Record r) Source #
at :: String -> Lens' (Record r) (Maybe (RowField r))

Map-style lookup and update by field name, defined in terms of ix. Viewing yields Just exactly for names in the row. Setting Just v updates the field when present; setting Nothing (deletion) and insertion of names not in the row are no-ops, because a record's shape is fixed by its row — the same caveat lens documents for fixed-size containers, and the reason ix (which cannot insert anyway) is the preferred interface.

Instance details

Methods

at :: Index (Record r) -> Lens' (Record r) (Maybe (IxValue (Record r))) #

(FieldNames r, AllFieldsAre (RowField r) r) => Ixed (Record r) Source # 
Instance details

Methods

ix :: Index (Record r) -> Traversal' (Record r) (IxValue (Record r)) #

AllFieldsAre a r => Each (Record ((n ':= a) ': r)) (Record ((n ':= a) ': r)) a a Source #
each :: AllFieldsAre a r => Traversal (Record ((n ':= a) ': r)) (Record ((n ':= a) ': r)) a a

The instance head fixes the element type to the row's field type, which is what the Each functional dependencies require: for a row-typed record the row determines the field types, so a type-changing traversal is impossible anyway. An empty record has no element type to traverse and deliberately gets no instance.

Instance details

Methods

each :: Traversal (Record ((n ':= a) ': r)) (Record ((n ':= a) ': r)) a a #

Field1 (Record ((n ':= a) ': r)) (Record ((n ':= a) ': r)) a a Source # 
Instance details

Methods

_1 :: Lens (Record ((n ':= a) ': r)) (Record ((n ':= a) ': r)) a a #

Field2 (Record ((n1 ':= a1) ': ((n2 ':= a2) ': r))) (Record ((n1 ':= a1) ': ((n2 ':= a2) ': r))) a2 a2 Source # 
Instance details

Methods

_2 :: Lens (Record ((n1 ':= a1) ': ((n2 ':= a2) ': r))) (Record ((n1 ':= a1) ': ((n2 ':= a2) ': r))) a2 a2 #

Field3 (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': r)))) (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': r)))) a3 a3 Source # 
Instance details

Methods

_3 :: Lens (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': r)))) (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': r)))) a3 a3 #

Field4 (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': ((n4 ':= a4) ': r))))) (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': ((n4 ':= a4) ': r))))) a4 a4 Source # 
Instance details

Methods

_4 :: Lens (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': ((n4 ':= a4) ': r))))) (Record ((n1 ':= a1) ': ((n2 ':= a2) ': ((n3 ':= a3) ': ((n4 ':= a4) ': r))))) a4 a4 #

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 # 
Instance details

Methods

_5 :: Lens (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 #

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 # 
Instance details

Methods

_6 :: Lens (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 #

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 # 
Instance details

Methods

_7 :: Lens (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 #

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 # 
Instance details

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 # 
Instance details

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 #