Conversation
Rebased onto `52671355` (the optic-class answer: `Z = Xo`, `Function1BroadcastOptic`, `RepresentativeIndex`, the positional write). That commit and this one answer different questions, and this rebase keeps upstream's machinery — and upstream's *idiom* — wherever it is better: - **kept** `Function1BroadcastOptic` as the kernel's per-index write hook (one value per position, no bundle read it did not build) and the bridge's `RepresentativeIndex[X0]` witness; - **added** the sum: the focus half is `F[A] | Broadcast[F, A]` (both cases `private[eo]`), with `MultiFocus.broadcast` / `.broadcast` as the public handles and `.foci` total for both (the index-free case carries its `F[A]` image); - **added** the kernel's remaining cases: a composite inner is written per position with the leftover *its own* read produced (upstream forges it, so a `fromLensF` inner NPEs), and a tabulating ∘ tabulating composite writes the exact inverse of its diagonal read. That last one needs `C =:= D`, which is a property of the *optic* — so it rides on the optic as `Optic.SameFocus` (mixed into every `andThen` composite from the call site's `summonFrom`, and into the monomorphic factories `representable` / `tuple` / `apply` with `Some(…)`), and the kernel reads it off the `inner` it is already handed. `AssociativeFunctor` and the composition algebra are untouched (zero diff vs upstream). - **deleted `unobserved`** — no write needs a stand-in any more. Its three historical sites are fixed at the source: the kernel records the inner leftovers its read observed, the bridge takes a real index witness, and the shape-collapsing `collectList` now hands the write the leftover its own read produced (`o.from((bundle.context, List(agg(bundle.foci))))`), which fixes a live NPE / `MatchError` on Lens / Prism / Optional provenances (probed before the fix). New coverage: `tests/.../MultiFocusCrossFamilySpec.scala` is the runtime twin of `CompositionMatrixSpec` — nine property blocks over every inbound provenance (generic factory, polymorphic factory, Iso/Lens/Prism/Optional bridges, a composite of two shipped optics) through `modify` / `replace` / `collectMap` / `collectWith` / `collectList` / `foldMap` / `headOption` / `length`, plus both composition directions across Iso / Lens / Prism / Optional with independently computed expectations. The collapsing aggregate is asserted only where the optic's leftover is count-agnostic; a composite's is the one named boundary (see the note). Measured against `52671355` (throwaway printing spec, removed again): `tuple ∘ tuple.modify(identity)` on `((1,2),(3,4))` → `((1,4),(1,4))` there, identity here; `replace(9)` → `((9,9),(9,9))` there, `((9,2),(3,9))` here; `tuple ∘ (shim ∘ shim).modify(_+1)` on `(1,2)` → `(2,2)` there, `(2,3)` here; a `fromLensF` inner NPEs there; `collectList` NPEs / MatchErrors there and works here. Upstream's own `MultiFocusFunction1Spec` is kept verbatim (14 examples / 349 expectations) and passes against this kernel; this branch's core `MultiFocusFunction1CompositionSpec` adds 13 blocks / 1003 expectations and `external/EoOpaqueSurfaceSpec` pins the public surface from outside the `eo` package tree. Gates (sbt 1.13.0, JDK 25): root `test` 237 examples / 0 failures, `scalafmtCheckAll`, `scalafixAll --check`, `scalafmtSbtCheck`, `benchmarks/scalafmtCheck`, `mimaReportBinaryIssues`, `githubWorkflowCheck`, `docs/mdoc` 0 errors, `docs/laikaSite`, `core/doc`.
Contributor
|
🚀 Cloudflare Pages preview for https://64847cb7.cats-eo-docs.pages.dev Branch alias: https://spike-grate-broadcast-sum.cats-eo-docs.pages.dev Built from commit |
Contributor
Benchmark A/BAllocation (B/op) — authoritative
442 more benchmarks
Timing (ns/op) — directional only, same-VM but shared runner
base_sha: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
MultiFocus[Function1[X0, *]](the Grate carrier) gets the constancy its types did not express, on top of #127's optic-class answer. Three pieces, and one deletion:F[A] | Broadcast[F, A], both casesprivate[eo].MultiFocus.broadcast/.broadcastare the public handles;.focistays total for both (the index-free case carries itsF[A]image), so existing consumers are unchanged. Constancy becomes a property of the data, so it survivesmap/collectWith/andThen.fromLensFinner NPEs), and a tabulating ∘ tabulating composite writes the exact inverse of its diagonal read. The latter needsC =:= D, which is a property of the optic, so it rides on the optic as the newOptic.SameFocuswitness (mixed into everyandThencomposite from that call site'ssummonFrom, and into the monomorphic factories).AssociativeFunctorhas zero diff.unobservedis deleted — no write needs a stand-in. The bridge takes aRepresentativeIndex(feat(core): supply the Grate bridge's index — the read stops being forged #127), the kernel records the inner leftovers it observed, and the collapsingcollectListnow hands the write the leftover its own read produced.Why — measured against 5267135
tuple ∘ tuple.modify(identity)(((1,2),(3,4)))((1,4),(1,4))(MF1 fails)((1,2),(3,4))tuple ∘ tuple.replace(9)((9,9),(9,9))((9,2),(3,9))tuple ∘ (shim ∘ shim).modify(_+1)((1,2))(2,2)(2,3)fromLensFinner whose write reads its leftoverNullPointerExceptioninGetReplaceLens.fromcollectListon Lens / Prism / Optional provenancesNullPointerException/MatchError: nullList(agg(foci))The probes were throwaway printing specs (removed again); every row is now pinned by the specs below rather than by a probe.
Tests
core/…/MultiFocusFunction1CompositionSpec— 13 blocks: positionalgrate ∘ iso, left- and right-associated composites,fromLensFinners, the tabulating ∘ tabulating laws, the documented type-changing fallback, associativity.tests/…/MultiFocusCrossFamilySpec— 17 blocks, the runtime twin ofCompositionMatrixSpec: every inbound provenance (generic factory, polymorphic factory, Iso/Lens/Prism/Optional bridges, a composite of two shipped optics) throughmodify/replace/collectMap/collectWith/collectList/foldMap/headOption/length; both composition directions across the families with independently computed expectations; the outbound direction intoModifyF(per provenance, writes matching the source) and theForget[List]escape over aFold.core/src/test/…/external/EoOpaqueSurfaceSpec— the public surface from outside theeopackage tree (the opaque sum neither leaks nor breaks inlining downstream).MultiFocusFunction1Specis unchanged and passes verbatim against this kernel (14 examples / 349 expectations).Impact and compatibility
Tuple2);AssociativeFunctoris untouched..foci/.context/applymust be recompiled (those bodies changed), andmfAssocFunction1.Zis no longerUnit. Source-compatible for callers; MiMa is off on the 0.x line.pair→mffor parameters typed as a carrier (7 files). It necessarily reaches five files upstream also owns (optics/Traversal.scala,optics/TraversalArityMacro.scala,jsoniter/JsoniterTraversal.scala,circe/…/CrossCarrierCompositionSpec.scala,tests/…/OpticsBehaviorSpec.scala); happy to split it into its own commit/PR on request.Review history worth knowing
Three review rounds changed the design, all reflected here: the same-focus write moved off the composition algebra onto the optic (
Optic.SameFocus, mirroring upstream's own witness idiom); the kernel's write path was flattened to three named cases plus one documented helper; andunobservedwas removed by fixing its three sites at the source. The measured drawbacks of that last fix (a composite's context is count-coupled, so the collapsing aggregate is only asserted for count-agnostic provenances) are named inMultiFocusCrossFamilySpecand in the note.Gates (sbt 1.13.0, JDK 25 Temurin)
root
test237 examples / 0 failures ·core/test33 specs · the two new sweeps (13 + 17 blocks) ·scalafmtCheckAll·scalafixAll --check·scalafmtSbtCheck·benchmarks/scalafmtCheck·mimaReportBinaryIssues·githubWorkflowCheck·docs/mdoc0 errors ·docs/laikaSite·core/doc.Residual value
What is left over
52671355, the measurement caveats and the recommendation (harvest the three correctness items into upstream's kernel; keep the sum only if.broadcastis wanted as public API) are recorded indocs/research/2026-09-30-grate-broadcast-constancy.md.