I have a final tagless DSL to build simple math expressions:
trait Entity[F[_]] {
def empty: F[Int]
def int(value: Int): F[Int]
}
trait Operation[F[_]] {
def add(a: F[Int], b: F[Int]): F[Int]
}
I wanted to implement a ZIO interpreter. Based on the module-pattern
guide, a possible implementation could look as follows:
type Entity = Has[Entity[UIO]]
object Entity {
val test: ULayer[Entity] =
ZLayer.succeed {
new Entity[UIO] {
override def empty: UIO[Int] =
ZIO.succeed(0)
override def int(value: Int): UIO[Int] =
ZIO.succeed(value)
}
}
def empty: URIO[Entity, Int] =
ZIO.accessM(_.get.empty)
def int(value: Int): URIO[Entity, Int] =
ZIO.accessM(_.get.int(value))
}
type Operation = Has[Operation[UIO]]
object Operation {
val test: ULayer[Operation] =
ZLayer.succeed {
new Operation[UIO] {
override def add(a: UIO[Int], b: UIO[Int]): UIO[Int] =
ZIO.tupled(a, b).map { case (x, y) => x + y }
}
}
def add(a: UIO[Int], b: UIO[Int]): URIO[Operation, Int] =
ZIO.accessM(_.get.add(a, b))
}
When building expressions with this implementation, one has to call provideLayer
repeatedly like this:
Operation.subtract(
Entity.empty.provideLayer(Entity.test),
Entity.int(10).provideLayer(Entity.test)
).provideLayer(Operation.test)
That looks more like an anti-pattern. What would be the most idiomatic or the most ZIO way to interpret DSLs?
Getting back to this question with a better understanding of ZIO I have found a solution. It is a workaround and not in the spirit of ZIO, nevertheless, I thought it might be worth sharing.
I updated the Operation implementation of ZIO:
type Operation = Has[Service[URIO[Entity, *]]]
object Operation {
val live: ULayer[Operation] =
ZLayer.succeed {
new Service[URIO[Entity, *]] {
override def add(a: URIO[Entity, Int])(b: URIO[Entity, Int]): URIO[Entity, Int] =
a.zip(b).map { case (x, y) => x + y }
}
}
}
def add(a: URIO[Entity, Int])(b: URIO[Entity, Int]): URIO[Entity with Operation, Int] =
ZIO.accessM(_.get[Service[URIO[Entity, *]]].add(a)(b))
This way Entity and Operation can be combined like this:
operation.add(entity.int(5))(entity.int(37))