Iso

An Iso[A, B] is to and from that are inverses. import conduit.Iso.* brings imap / xmap on Lens: view an Int field as a String (or any other type) without storing the viewed type in the model.

Lawfulness is on you. If from(to(a)) != a, lens laws on the derived lens fail in the places the iso loses information.

imap / xmap

import conduit.Iso.*

val intToStr = Iso[Int, String](_.toString, _.toInt)
val asStr    = Optics[Box](_.n).imap(intToStr)
// xmap(to, from) is imap(Iso(to, from))
Mermoid.diagram(round)
toStringtoInt
{
  val box = Box(42)
  (asStr.get(box), asStr.set(box, "100").n)
}
(42,100)

Live: store Int, show String

The model is Box(n: Int). Buttons write through the iso (SetStr) or the raw Int lens (Inc). Both stay in sync.

for
  (_, ctx) <- DocsRuntime.live(Box(7))(handler)
  n        <- ctx.squawk(_.n)
yield E.div(
  E.p("stored Int: ", E.strong(n.map(_.toString))),
  E.button(Events.onClick(_ => ctx(BoxOp.Inc)), "Inc"),
  E.button(Events.onClick(_ => ctx(BoxOp.SetStr("0"))), "Set 0"),
  E.button(Events.onClick(_ => ctx(BoxOp.SetStr("42"))), "Set 42"),
)

stored Int: 7

for
  c <- Conduit(Box(7))(handler)
  _ <- c(BoxOp.SetStr("10"), BoxOp.Inc)
  _ <- c.run()
  s <- c.currentModel
yield (s.n, asStr.get(s))
(11,11)

reverse

iso.reverse swaps to / from. Iso.id[A] is the identity. Compose isos yourself if you need a pipeline; Conduit only ships imap on one lens.

Mermoid.diagramInteractive(round, initialWidth = 480)
viewport 480px
toStringtoInt