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:
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:
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.