Exit e-book
Show all chapters
07
Effectful and Transactional Optics
07. 
Effectful and Transactional Optics
Improve your focus with ZIO optics
07

Effectful and Transactional Optics

So far we’ve explored Pure Optics, but as was previously mentioned, ZIO Optics also allows us to work with Effectful and Transactional Optics.

Effectful Optics work exactly the same way as Pure Optics, but the OpticResult returned by the getter and setter is a ZIO effect, so we have:

case class Optic[
  -GetWhole,
  -SetWholeBefore,
  -SetPiece,
  +GetError,
  +SetError,
  +GetPiece,
  +SetWholeAfter
](
  getOptic: GetWhole => IO[(GetError, SetWholeAfter), GetPiece],
  setOptic: SetPiece => SetWholeBefore => IO[(SetError, SetWholeAfter), SetWholeAfter]
)

To work with Effectful Optics, just include the following import:

import zio.optics.opticsm._

Transactional Optics also work exactly the same as Pure Optics, but the OpticResult returned by the getter and setter is an STM effect, so we have:

case class Optic[
  -GetWhole,
  -SetWholeBefore,
  -SetPiece,
  +GetError,
  +SetError,
  +GetPiece,
  +SetWholeAfter
](
  getOptic: GetWhole => STM[(GetError, SetWholeAfter), GetPiece],
  setOptic: SetPiece => SetWholeBefore => STM[(SetError, SetWholeAfter), SetWholeAfter]

To work with Transactional Optics, just include the following import:

import zio.optics.toptics._

So, for example, let’s say we have a nested TMap (which is a data structure from ZIO STM) like this:

import zio.stm._

val tmap: USTM[TMap[String, Either[String, Int]]] =
  TMap.make(
    ("test1", Left("hello")),
    ("test2", Right(1))
  )

And we want to write a method which, given a TMap and key, increments the corresponding value by 1, in the case the value is an integer. Without ZIO Optics, we would have something like this:

def updateMap(
  tmap: TMap[String, Either[String, Int]],
  key: String
): STM[String, TMap[String, Either[String, Int]]] =
  for {
    optionEither <- tmap.get(key)
    either       <- STM.fromOption(optionEither) <> STM.fail(s"tmap does not contain key $key")
    _ <- either match {
          case Left(_)    => STM.fail("Not an Int")
          case Right(int) => tmap.put(key, Right(int + 1))
        }
  } yield tmap

But, with ZIO Optics we have:

import zio.optics.toptics._

def updateMap2(
 tmap: TMap[String, Either[String, Int]],
 key: String
): STM[OpticFailure, TMap[String, Either[String, Int]]] = {
  val optic: Optional[TMap[String, Either[String, Int]], Int] =
    TOptics.key(key) >>> Optic.right
  optic.update(tmap)(_ + 1)
}

Looks a lot better! And as you can see, the way to work with Transactional Optics is pretty much the same as when we work with Pure Optics. The only new thing here is we have TOptics.key, which is a specialized Prism that works with TMap.

PREVIOUS
Chapter
06
NEXT
Chapter
08