Exit e-book
Show all chapters
Polymorphic Optics
Polymorphic Optics
Improve your focus with ZIO optics

Polymorphic Optics

ZIO Optics provides more polymorphic versions of each Optic we have discussed previously. 

For example, there is a more powerful type of Lens, called ZLens, which is represented by the following type alias:

type ZLens[-S, +T, +A, -B] = Optic[S, S, B, Nothing, Nothing, A, T]

The simplified signature of Optic for ZLens would therefore now be like this:

case class ZLens[-S, +T, +A, -B](
  getOptic: S => Either[Nothing, A],
  setOptic: B => S => Either[Nothing, T]

And simplifying even further:

case class ZLens[-S, +T, +A, -B](
  getOptic: S => A,
  setOptic: B => S => T

You can see that the getter of ZLens is the same as the one in Lens. However, the setter is more powerful, because it can transform the whole of type S to a new whole of type T, by setting a value of type B. So, basically, a Lens is just a restricted ZLens, because it can’t change the type of the whole data structure. The relationship between both of them is this:

ole data structure. The relationship between both of them is this:
type Lens[S, A] = ZLens[S, S, A, A]

Similarly, we have ZPrism:

type ZPrism[-S, +T, +A, -B] = Optic[S, Any, B, OpticFailure, Nothing, A, T]
type Prism[S, A] = ZPrism[S, S, A, A]

And ZIso:

type ZIso[-S, +T, +A, -B] = Optic[S, Any, B, Nothing, Nothing, A, T]
type Iso[S, A] = ZIso[S, S, A, A]

And ZOptional:

type ZOptional[-S, +T, +A, -B]  = Optic[S, S, B, OpticFailure, OpticFailure, A, T]
type Optional[S, A] = ZOptional[S, S, A, A]

And ZTraversal:

type ZTraversal[-S, +T, +A, -B] = Optic[S, S, Chunk[B], OpticFailure, OpticFailure, Chunk[A], T]
type Traversal[S, A] = ZTraversal[S, S, A, A]


Example of Polymorphic Optics

Let’s say we have the following ADTs:

final case class Item[C <: Currency](description: String, price: BigDecimal, currency: C)

sealed trait Currency
object Currency {
  case object Dollar extends Currency
  case object Euro   extends Currency

  type Dollar = Dollar.type
  type Euro   = Euro.type

Item is a product type which models an item inside a shopping cart. What’s interesting about it is that it’s a polymorphic ADT, parameterized by a C type.

Let’s say we want to create a Lens for accessing the currency inside an Item. However, we want this Lens not just to be able to change the value of the currency, but also its type, which in turn would change the type of the whole Item. For instance, if we have an Item[Currency.Dollar], that means its currency is equal to Currency.Dollar. And, if we change the currency to Currency.Euro, the Item type would change to Item[Currency.Euro]. A simple Lens is not powerful enough for this case, because the setter doesn’t allow us to change the type of the piece we want to set, nor the type of the whole. That means we need a ZLens! The way of constructing it is very similar to how we construct a Lens:

final case class Item[C <: Currency](description: String, price: BigDecimal, currency: C)
object Item {
 def currency[Old <: Currency, New <: Currency]: ZLens[Item[Old], Item[New], Old, New] =
     item => Right(item.currency),
     newCurrency => item => Right(item.copy(currency = newCurrency))

You can see the ZLens we have just defined allows us to change from an Old currency type to a New currency type.

And now, you can see the way of using a ZLens is exactly the same as with Lens:

val item1: Item[Currency.Dollar] = Item("Some book", 10.0, Currency.Dollar)

// Get `currency` from item1
val currency1: Either[Nothing, Currency.Dollar] = Item.currency.get(item1)
// Right(Currency.Dollar)

// Change `currency` in item1 to Euro
val item2: Either[Nothing, Item[Currency.Euro]] = Item.currency.set(Currency.Euro)(item1)
// Right(Item(Some book,10.0,Euro))