Construct ZLayers automagically, with friendly compile-time hints!
// build.sbt
libraryDependencies += "io.github.kitlangton" %% "zio-magic" % "0.1.11"
// Given a dependency graph (Cake needs Chocolate & Flour, which in turn need Spoon)*
//
// Cake
// / \
// Chocolate Flour
// | |
// Spoon Spoon
//
// *Not an actual recipe.
def run(args: List[String]): URIO[ZEnv, ExitCode] = {
// An effect requiring Cake and Console. Yum!
val program: URIO[Console with Cake, Unit] =
Cake.isDelicious.flatMap { bool => console.putStrLn(s"Cake is delicious: $bool") }
// The old way
val manually: ULayer[Cake with Console] =
((Spoon.live >>> Flour.live) ++ (Spoon.live >>> Chocolate.live)) >>> Cake.live ++ Console.live
// The magical way (The order doesn't matter)
val magically: UIO[Unit] =
program.provideMagicLayer(
Cake.live,
Flour.live,
Chocolate.live,
Spoon.live,
Console.live
)
magically.exitCode
}
And if you leave something off, a compile time clue!
val magically: UIO[Unit] =
program.provideMagicLayer(
Cake.live,
//Flour.live, <-- Oops
Chocolate.live,
Spoon.live,
Console.live
)
๐ช ZLayer Magic Missing Components
๐ช
๐ช provide zio.magic.Example.Flour.Service
๐ช for Cake.live
Versus leaving out a dependency when manually constructing your layer...
val manually: ULayer[Cake with Console] =
(Flour.live ++ (Spoon.live >>> Chocolate.live)) >>> Cake.live ++ Console.live
// ^ A Spoon is missing here!
type mismatch;
found : zio.ZLayer[zio.magic.Example.Spoon.Spoon with Any,Nothing,zio.magic.Example.Cake.Cake with zio.console.Console]
(which expands to) zio.ZLayer[zio.Has[zio.magic.Example.Spoon.Service] with Any,Nothing,zio.Has[zio.magic.Example.Cake.Service] with zio.Has[zio.console.Console.Service]]
required: zio.ULayer[zio.magic.Example.Cake.Cake with zio.console.Console]
(which expands to) zio.ZLayer[Any,Nothing,zio.Has[zio.magic.Example.Cake.Service] with zio.Has[zio.console.Console.Service]]
((Flour.live) ++ (Spoon.live >>> Chocolate.live)) >>> Cake.live ++ Console.live
You can also directly construct a ZLayer (However you must annotate the call to ZLayer.fromMagic[LikeThis]
, because macros).
val layer = Zlayer.fromMagic[Flour with Console](Console.live, Flour.live, Spoon.live)
To construct URLayer[In, Out]
use Zlayer.fromSomeMagic[In, Out]
this way:
val layer = Zlayer.fromSomeMagic[CommonEnv, Flour with Console](Console.live, Flour.live, Spoon.live)
Alternatively you can provide environment partially with provideSomeMagicLayer[Rest](l1, l2, l3)
- similarly to .provideSomeLayer
.
There's also .provideCustomMagicLayer
for which behaves similarly to .provideCustomLayer
, only it also provides ZEnv.any
to all transitive dependencies.
val program: URIO[Console with Car, Unit] = ???
val carLayer: URLayer[Blocking with Wheels, Car] = ???
val wheelLayer: ULayer[Wheels] = ???
// The ZEnv you use later will provide both Blocking to carLayer and Console to the program
val provided: URIO[ZEnv, Unit] =
program.provideCustomMagicLayer(carLayer, wheelLayer)
provideMagicLayer
, provideCustomMagicLayer
, provideSomeMagicLayer
, provideMagicLayerShared
, provideCustomMagicLayerShared
and provideSomeMagicLayerShared
all work for zio-test's Spec
.
Try ZLayer.fromMagicDebug[Cake]
or ZLayer.fromSomeMagicDebug[Blocking with Console, Cake]
to print out a pretty graph! Ooh la la!
Your Delicately Rendered Graph
Cake.live
โโโโโโโโโดโโโโโโโโโโโโโโโ
Chocolate.live Chocolate.live
โโโโโโโโดโโโโโโโโ โ
Spoon.live Console.live Spoon.live
โ โ
Blocking.live Blocking.live
Let me know if you can think of any helpful variants, and I'll give 'em a whirl!