Exit e-book
Show all chapters
05
Bonus section: Additional ways for combining ZLayers
05. 
Bonus section: Additional ways for combining ZLayers
Mastering Modularity in ZIO with Zlayer
05

Bonus section: Additional ways for combining ZLayers

So far we have explored how to combine ZLayers using the ++ operator for horizontal composition and the >>> operator for vertical composition, and most of the time those are the only operators you will need. However, there are other operators that you could use for certain situations.

The <> operator (which is a symbolic alias for the orElse operator), allows two layers to be combined, so that:

  • If the first layer succeeds when building an output, then the first layer is returned.
  • If the first layer fails when building an output, then the second layer is returned.

In the following code for example, we are combining two layers, and because the first one always fails when building an output, the second layer will be returned:

val layer: ULayer[Has[ConfirmCommandParser]] =
  ZLayer.fail("Error") <> ConfirmCommandParserLive.layer

Next, we have the <&> operator (which is a symbolic alias for the zipPar operator), which is also a horizontal composition operator.  However, there is a slight difference. For example, if we use the ++ operator we get something like this:

val confirmModeDeps: ULayer[Has[ConfirmCommandParser] with Has[ConfirmView]] =
 ConfirmCommandParserLive.layer ++ ConfirmViewLive.layer

And, if we use the <&> operator, the output type of the resulting layer will be a tuple instead:

val confirmModeDeps: ULayer[(Has[ConfirmCommandParser], Has[ConfirmView])] =
 ConfirmCommandParserLive.layer <&> ConfirmViewLive.layer

Another operator we can use is +!+, which is for doing unsafe horizontal composition. But why is it unsafe? Well, because when using this operator the right-hand side can overwrite the left-hand side without us knowing. Let’s see an example of how this could happen, imagine we have this:

val confirmView: ULayer[Has[ConfirmView]] =
  ConfirmCommandParserDummy.layer ++ ConfirmViewLive.layer

Where ConfirmCommandParserDummy would look like this:

final case class GameCommandParserDummy() extends GameCommandParser {
  def parse(input: String): IO[AppError, GameCommand] = IO.fail(ParseError)
}
object GameCommandParserDummy {
  val layer: ULayer[Has[GameCommandParser]] = (GameCommandParserDummy.apply _).toLayer
}

As shown above, we are ascribing the confirmView variable to a type ULayer[Has[ConfirmView]] instead of ULayer[Has[ConfirmCommandParser] with Has[ConfirmView]], and the compiler will be happy with it, because:

  • ConfirmCommandParser with ConfirmView is a subtype of ConfirmView
  • Since ULayer is covariant in its parameter, that means ULayer[Has[ConfirmCommandParser] with Has[ConfirmView]] is a subtype of ULayer[Has[ConfirmView]].

Having said that, confirmView has just a ConfirmView service inside it, according to the compiler. However, we know that in reality it also has a ConfirmCommandParser dummy implementation.

So let’s see what happens if we do this:

val confirmModeDeps: ULayer[Has[ConfirmCommandParser] with Has[ConfirmView]] =
  ConfirmCommandParserLive.layer +!+ confirmView

Well, what will happen is that the ConfirmCommandParserLive implementation will be overwritten by confirmView, because inside of it there’s a hidden ConfirmCommandParserDummy implementation. That’s really bad, because that means we could end up running a dummy implementation in a production environment! This scenario won’t happen if we use the ++ operator, because it always prunes the right hand side before doing the composition. And what is pruning about? In this case, the type of confirmView says that it just contains a ConfirmView service inside it, so pruning ensures that’s actually true, removing the hidden ConfirmCommandParserDummy so that it won’t overwrite the ConfirmCommandParserLive implementation by accident.

After having seen the dangers of using the +!+ operator you must be wondering: why would I ever use it? And the answer is: if you really know what you are doing and if you want a more performant composition of layers (because there are no pruning steps to be executed), then use +!+

Finally, we have the >+> operator. Here’s an example of its use:

val confirmModeDeps: ULayer[Has[ConfirmCommandParser] with Has[ConfirmView]] =
  ConfirmCommandParserLive.layer ++ ConfirmViewLive.layer

val confirmModeNoDeps: ULayer[Has[ConfirmCommandParser] with Has[ConfirmView] with Has[ConfirmMode]] =
  confirmModeDeps >+> ConfirmModeLive.layer

The >+> operator is equivalent to a combination of horizontal and vertical composition, like this:

val confirmModeDeps: ULayer[Has[ConfirmCommandParser] with Has[ConfirmView]] =
  ConfirmCommandParser.live ++ ConfirmView.live

val confirmModeNoDeps: ULayer[Has[ConfirmCommandParser] with Has[ConfirmView] with Has[ConfirmMode]] =
  confirmModeDeps ++ (confirmModeDeps >>> ConfirmMode.live)

And, if we compare this to using the >>> operator:

val confirmModeDeps: ULayer[Has[ConfirmCommandParser] with Has[ConfirmView]] =
  ConfirmCommandParserLive.layer ++ ConfirmViewLive.layer

val confirmModeNoDeps: ULayer[Has[ConfirmMode]] = confirmModeDeps >>> ConfirmModeLive.layer

You can realize the difference of using the  >+> operator is that the outputs of confirmModeDeps were included in the outputs of confirmModeNoDeps. So, if that’s something you need, now you know which operator to use.

PREVIOUS
Chapter
04
NEXT
Chapter
06