FastEq

FastEq[A] is optional. Conduit uses it in two places: dispatch (skip every listener if the model did not change) and per listener (skip the callback if that slice did not change). With no given, it is == via FastEq.fromEquals.

noChange never notifies

Op.NoOp uses noChange (dirty = false). The listener on _.value fires only for Inc.

Mermoid.diagram(gates)
cleandirtyequalchangedequalchanged
for
  c     <- Conduit(Counter(0))(handler)
  count <- Ref.make(0)
  _     <- c.subscribe(_.value)(_ => count.update(_ + 1))
  _     <- c(Op.NoOp, Op.NoOp, Op.Inc, Op.NoOp)
  _     <- c.run()
  fired <- count.get
yield fired
1

Live: skip vs notify

Same writes value to itself (update(identity)). Structural == is true, so the listener should not fire. Inc does. The badge is how many times the count listener ran.

for
  (c, ctx) <- DocsRuntime.live(Counter(0))(handler)
  fired    <- sq(0)
  _        <- c.subscribe(_.value)(_ => fired.update(_ + 1).unit)
  value    <- ctx.squawk(_.value)
yield E.div(
  E.p("value ", E.strong(value.map(_.toString)), "  listener fires ", E.strong(fired.map(_.toString))),
  E.button(Events.onClick(_ => ctx(Op.Inc)), "Inc"),
  E.button(Events.onClick(_ => ctx(Op.NoOp)), "NoOp (clean)"),
  E.button(Events.onClick(_ => ctx(Op.SetSame)), "Set same value"),
)

value 0 listener fires 0

fromVersion

FastEq.fromVersion(_.version) treats two models as equal when the version field matches, even if other fields differ. Put the given on the model companion; Conduit.apply takes [M: FastEq], so dispatch uses it. QuietSet changes value without bumping version, so a whole-model listener skips. Bump notifies once.

for
  c     <- Conduit(Versioned(0, 0L))(verHandler)
  fired <- Ref.make(0)
  _     <- c.subscribe(Optics[Versioned])(_ => fired.update(_ + 1))
  _     <- c(VerOp.QuietSet(99), VerOp.Bump)
  _     <- c.run()
  n     <- fired.get
  s     <- c.currentModel
yield (n, s.value, s.version)
(1,99,1)
Mermoid.diagramInteractive(gates, initialWidth = 520)
viewport 520px
cleandirtyequalchangedequalchanged

Factories

FactoryWhen
fromEqualsExplicit == (same as the fallback)
instance((a,b) => …)Full control
withReferenceEquality(fb)eq first; lenses keep sibling refs
fromVersion(_.version)Monotonic version field
fromHash(_.cachedHash)Precomputed hash
withDirtyFlag(_.isDirty, fb)Clean-clean short-circuits to equal
derived / derives FastEqSame as fromEquals