Skip to content

Refactoring 2/35: Replace mutable fields with lenses - #13

Merged
kryptt merged 6 commits into
masterfrom
refactoring/replace-mutable-fields-with-lenses
Sep 3, 2026
Merged

kryptt merged 6 commits into
masterfrom
refactoring/replace-mutable-fields-with-lenses

Conversation

@kryptt

@kryptt kryptt commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

Refactoring 2 of 35 — Replace mutable fields with lenses (with its dual, inline the lens)

Second entry of the catalogue at /refactorings/replace-mutable-fields-with-lenses/. Reworked in response to review: the entry now teaches optics (lens · prism · traversal) and the separation of how to reach a field from what to do with it, with correctness "lawful by construction" and law-solvers covered in Verification.

Page

  • Optics framing in the intro, Motivation, The move, and The functional reading (now an optics section — lens/prism/traversal, one .andThen shape, composing smaller portable optics).
  • eo as reference point: links eo.constructive.dev and the cookbook throughout; the three jobs optics do best; auto-derivable optics (eo/monocle/lens TH).
  • Inverse justified by decoupling not earning its keep / no cross-domain boundaries.
  • Pitfalls reduced to the real ones: accidental complexity, rebuilding beyond the focus, laziness/evaluation count, wrong family (type error not runtime).
  • Verification: property equality + hedgehog, mutation-checked; laws are by construction, and libraries ship law-solvers (cats-eo-laws, monocle-law, genvalidity-hspec-optics).

Examples (rebuilt around eo's "navigate structures" recipes, self-contained optics in Scala + Haskell)

  1. 01-rename-var — prism .andThen lens: upper the name of one Var node; misses pass through.
  2. 02-rename-tree — the same varName optic at every tree node via bottom-up everywhere.
  3. 03-bump-oks — each .andThen prism .andThen lens (eo cookbook "visit through arbitrary structure"): bump only the successes of a batch.

Verification

  • all properties passed — 12 properties (6 per language, equivalence + purpose).
  • Mutation-checked per example in both languages (sign-flip / wrong-target mutants) — all caught.
  • Jekyll build exit 0; no horizontal overflow at 1200px / 360px; all sources ≤ 72 chars, no Liquid braces.
  • Every source runs dependency-free (the tiny optic encoding is included in the After files).

Second entry of the refactoring catalogue, with its dual (inline the
lens). Three Before/After examples in Scala 3 and Haskell, each proven
equivalent by hedgehog properties the reviewer mutation-checked, plus
the three lens laws as second properties, a koan diagram and three
example diagrams, and 14 references verified against primary sources.

- pages/refactorings/replace-mutable-fields-with-lenses.md and /sources/
- _data/refactorings.yml: add slug for the entry
- _config.yml: exclude the new sources dir
- .claude/skills/refactoring-entry/LESSONS.md: lessons from this entry
  (result-value comparison, composed-path design, partial-lens pitfall)
@github-actions

github-actions Bot commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-09-03 23:31 UTC

- Examples rebuilt around the eo cookbook navigate-structures trio:
  prism×lens on one node, the same optic at every tree node, and a
  traversal×prism×lens sparse walk over a batch — all self-contained
  optic encodings running in both Scala 3 and Haskell with no deps.
- Page rewritten: optics framing (how-to-reach vs what-to-do), lawful
  by construction + law-solvers, inverse justified by decoupling not
  being worth it / no cross-domain boundaries, functional reading as
  optics (links eo), pitfalls are accidental complexity + rebuilding
  beyond the focus, verification cites cats-eo-laws / monocle-law /
  genvalidity-hspec-optics.
- All 12 properties pass both languages; mutants caught.
- Diagrams redrawn for the three new examples.
- Move the optic building blocks (Lens/Prism/PartialLens/each) and the
  hedgehog spec runner out of the examples into pages/.../shared/,
  compiled by run.sh (scala-cli sources + runghc -i) but never shown on
  the page. Each Before/After/Spec is now the move itself; the page
  notes the shared setup is hidden.
- SKILL.md/brief.md: fold in the rule (shared setup is accidental
  complexity if shown; hide it in non-published shared/), and generalize
  the skill for any agent — the workflow is described as phases
  (research · examples · review · diagrams), with the Claude Workflow
  script as one optional orchestrator and the gh step generalized.
- LESSONS.md: record the round.
- shared/Optics (Scala + Haskell) gains a Plated class/instance with
  descend + everywhere; example 2 declares its Plated[Expr] instance
  and lets everywhere do the walk instead of a hand-written recursion.
- Motivation: the smell is multiple long methods doing too much, with
  reach-the-value complexity mixed into the change code.
- Page example-2 prose updated to say the walk comes from Plated (eo's
  visit-across-whole-trees recipe).
Generalize the Plated lesson from 'don't hand-write the recursion' to
'never hand-write intermediate helper methods in an example' (like the
hand-defined everywhere). Folded into SKILL.md §3 and brief.md — the
helper belongs in shared/ or a library; the example declares only the
instance (which fields recurse).
@kryptt
kryptt merged commit d43b40b into master Sep 3, 2026
1 check passed
@kryptt
kryptt deleted the refactoring/replace-mutable-fields-with-lenses branch September 3, 2026 23:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant