polytropos-microlens
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.Microlens

Description

Class-based optics integration for developers who use microlens specifically to avoid transitive dependency bloat: this package depends strictly on base, polytropos-core, and microlens.

Architectural Overview & Rationale

Unlike full lens, which brings in a large dependency graph, microlens provides the core lens combinators in a lightweight package.

In polytropos-microlens: * Name-based field access goes through fieldLens, whose FieldIndex constraint is solved to a literal slot index by polytropos-plugin. * The each instance traverses every field of a homogeneous record (where all fields have the same type, witnessed by AllFieldsAre) in a single pass over the flat SmallArray.

When to Use This Module

  • When your project prioritizes fast build times and minimal transitive dependencies, and requires basic lens access to polytropos records.

When Not to Use This Module

Worked Example

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -fplugin=Polytropos.Plugin #-}

import Lens.Micro ((^.), (%~), (&))
import Polytropos
import Polytropos.Microlens

-- A genuinely heterogeneous record:
type User = Record '["name" ':= String, "score" ':= Int]

user :: User
user = REC { name = "Alice", score = 95 }

-- Accessing a field via heterogeneous fieldLens:
userName :: String
userName = user ^. fieldLens (Field @"name")  -- "Alice"

-- Modifying a field:
user' :: User
user' = user & fieldLens (Field @"score") %~ (+ 5)  -- score = 100
Synopsis

Field Lenses

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.

Homogeneity Evidence

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. This is what lets each be a genuine traversal with a single element type instead of an erased forall-traversal.

Equations

AllFieldsAre _1 ('[] :: [Pair Symbol Type]) = () 
AllFieldsAre a ((n ':= a) ': rest) = AllFieldsAre a rest 

Orphan instances

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

Homogeneous traversal across all record fields.

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. Implemented over the flat Rep payload in one pass: read the slots, run the effectful function over the values, and rebuild the record with productRep. For a record with N fields this is O(N) with no intermediate tree representation.

An empty record has no element type to traverse; each deliberately does not apply to Record '[].

Instance details

Methods

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