如何用 ZIO 解释最终的无标签 DSL?
How to interpret a final tagless DSL with ZIO?
我有一个最终的无标记 DSL 来构建简单的数学表达式:
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]
}
我想实现一个 ZIO 解释器。基于 module-pattern
guide,可能的实现如下所示:
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))
}
使用此实现构建表达式时,必须像这样重复调用 provideLayer
:
Operation.subtract(
Entity.empty.provideLayer(Entity.test),
Entity.int(10).provideLayer(Entity.test)
).provideLayer(Operation.test)
这看起来更像是一种反模式。解释 DSL 的最惯用或最 ZIO 方式是什么?
从这个问题中不太清楚你想要达到什么目的,但让我试着回答一下。
ZIO 的 R
参数与构建 DSL 没有直接关系。
一旦你构建了你的 DSL,R
可能会帮助你以符合人体工程学的方式将它传递给你的计算(但可能不会)。
DSL 不是一个精确的术语,但 ZIO
仍然不太可能帮助您构建它。
DSL 通常基于普通的可自省数据类型(所谓的初始编码)或具有大量 F
的抽象数据类型(最终编码)。 ZIO
数据类型既不抽象也不自省。
在更好地了解 ZIO 的情况下回到这个问题,我找到了解决方案。这是一种解决方法,不符合 ZIO 的精神,不过,我认为它可能值得分享。
我更新了 ZIO 的 Operation 实现:
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))
这样实体和操作可以这样组合:
operation.add(entity.int(5))(entity.int(37))
我有一个最终的无标记 DSL 来构建简单的数学表达式:
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]
}
我想实现一个 ZIO 解释器。基于 module-pattern
guide,可能的实现如下所示:
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))
}
使用此实现构建表达式时,必须像这样重复调用 provideLayer
:
Operation.subtract(
Entity.empty.provideLayer(Entity.test),
Entity.int(10).provideLayer(Entity.test)
).provideLayer(Operation.test)
这看起来更像是一种反模式。解释 DSL 的最惯用或最 ZIO 方式是什么?
从这个问题中不太清楚你想要达到什么目的,但让我试着回答一下。
ZIO 的
R
参数与构建 DSL 没有直接关系。 一旦你构建了你的 DSL,R
可能会帮助你以符合人体工程学的方式将它传递给你的计算(但可能不会)。DSL 不是一个精确的术语,但
ZIO
仍然不太可能帮助您构建它。 DSL 通常基于普通的可自省数据类型(所谓的初始编码)或具有大量F
的抽象数据类型(最终编码)。ZIO
数据类型既不抽象也不自省。
在更好地了解 ZIO 的情况下回到这个问题,我找到了解决方案。这是一种解决方法,不符合 ZIO 的精神,不过,我认为它可能值得分享。
我更新了 ZIO 的 Operation 实现:
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))
这样实体和操作可以这样组合:
operation.add(entity.int(5))(entity.int(37))