为密封抽象特征的子类调用 Scala 隐式
Summoning Scala implicits for subclasses of sealed abstract trait
我正在使用两个 Scala 库,它们都依赖于隐式参数来为案例 classes 提供 codecs/marshallers(有问题的库是 msgpack4s 和 op-rabbit)。一个简化的例子如下:
sealed abstract trait Event
case class SomeEvent(msg: String) extends Event
case class OtherEvent(code: String) extends Event
// Assume library1 needs Show and library2 needs Printer
trait Show[A] { def show(a: A): String }
trait Printer[A] { def printIt(a: A): Unit }
object ShowInstances {
implicit val showSomeEvent = new Show[SomeEvent] {
override def show(a: SomeEvent) =
s"SomeEvent: ${a.msg}"
}
implicit val showOtherEvent = new Show[OtherEvent] {
override def show(a: OtherEvent) =
s"OtherEvent: ${a.code}"
}
}
一个库的打印机可以是通用的,前提是另一个库有隐式显示可用:
object PrinterInstances {
implicit def somePrinter[A: Show]: Printer[A] = new Printer[A] {
override def printIt(a: A): Unit =
println(implicitly[Show[A]].show(a))
}
}
我想提供一个 API 来抽象底层库的细节 - 调用者应该只需要将案例 class,内部传递给 API 实现相关应该召唤 implicits。
object EventHandler {
private def printEvent[A <: Event](a: A)(implicit printer: Printer[A]): Unit = {
print("Handling event: ")
printer.printIt(a)
}
def handle(a: Event): Unit = {
import ShowInstances._
import PrinterInstances._
// I'd like to do this:
//EventHandler.printEvent(a)
// but I have to do this
a match {
case s: SomeEvent => EventHandler.printEvent(s)
case o: OtherEvent => EventHandler.printEvent(o)
}
}
}
EventHandler.handle() 方法中的注释指出了我的问题 - 有没有办法让编译器 select 为我正确隐含?
我怀疑答案是否定的,因为在编译时编译器不知道事件句柄()的哪个子class会收到,但我想看看有没有别的办法。在我的实际代码中,我控制并可以更改 PrinterInstances 代码,但我无法更改 printEvent 方法的签名(由其中一个库提供)
*编辑:我认为这与 Provide implicits for all subtypes of sealed type 相同。那里的答案已经将近 2 年了,我想知道它是否仍然是最好的方法?
你必须在某处进行模式匹配。在 Show
实例中执行:
implicit val showEvent = new Show[Event] {
def show(a: Event) = a match {
case SomeEvent(msg) => s"SomeEvent: $msg"
case OtherEvent(code) => s"OtherEvent: $code"
}
}
如果您确实需要 SomeEvent
和 OtherEvent
的单独实例,您可以在不同的对象中提供它们,以便单独导入它们。
如果 Show
被定义为逆变(即 trait Show[-A] { ... }
,泛型上有一个减号)那么一切都可以开箱即用,并且 Show[Event]
可以用作a Show[SomeEvent]
(就此而言,作为 Show[OtherEvent]
)。
如果不幸的是 Show
没有写成逆变的,那么我们可能不得不在我们这边做一些比我们想要的更多的杂耍。我们可以做的一件事是将我们所有的 SomeEvent
值声明为简单的 Event
s,相对于 val fooEvent: Event = SomeEvent("foo")
。那么fooEvent
就可以显示了。
在上述技巧的更极端版本中,我们实际上可以隐藏我们的继承层次结构:
sealed trait Event {
def fold[X]( withSomeEvent: String => X,
withOtherEvent: String => X ): X
}
object Event {
private case class SomeEvent(msg: String) extends Event {
def fold[X]( withSomeEvent: String => X,
withOtherEvent: String => X ): X = withSomeEvent(msg)
}
private case class OtherEvent(code: String) extends Event {
def fold[X]( withSomeEvent: String => X,
withOtherEvent: String => X ): X = withOtherEvent(code)
}
def someEvent(msg: String): Event = SomeEvent(msg)
def otherEvent(code: String): Event = OtherEvent(code)
}
Event.someEvent
和 Event.otherEvent
允许我们构造值,fold
允许我们进行模式匹配。
我正在使用两个 Scala 库,它们都依赖于隐式参数来为案例 classes 提供 codecs/marshallers(有问题的库是 msgpack4s 和 op-rabbit)。一个简化的例子如下:
sealed abstract trait Event
case class SomeEvent(msg: String) extends Event
case class OtherEvent(code: String) extends Event
// Assume library1 needs Show and library2 needs Printer
trait Show[A] { def show(a: A): String }
trait Printer[A] { def printIt(a: A): Unit }
object ShowInstances {
implicit val showSomeEvent = new Show[SomeEvent] {
override def show(a: SomeEvent) =
s"SomeEvent: ${a.msg}"
}
implicit val showOtherEvent = new Show[OtherEvent] {
override def show(a: OtherEvent) =
s"OtherEvent: ${a.code}"
}
}
一个库的打印机可以是通用的,前提是另一个库有隐式显示可用:
object PrinterInstances {
implicit def somePrinter[A: Show]: Printer[A] = new Printer[A] {
override def printIt(a: A): Unit =
println(implicitly[Show[A]].show(a))
}
}
我想提供一个 API 来抽象底层库的细节 - 调用者应该只需要将案例 class,内部传递给 API 实现相关应该召唤 implicits。
object EventHandler {
private def printEvent[A <: Event](a: A)(implicit printer: Printer[A]): Unit = {
print("Handling event: ")
printer.printIt(a)
}
def handle(a: Event): Unit = {
import ShowInstances._
import PrinterInstances._
// I'd like to do this:
//EventHandler.printEvent(a)
// but I have to do this
a match {
case s: SomeEvent => EventHandler.printEvent(s)
case o: OtherEvent => EventHandler.printEvent(o)
}
}
}
EventHandler.handle() 方法中的注释指出了我的问题 - 有没有办法让编译器 select 为我正确隐含?
我怀疑答案是否定的,因为在编译时编译器不知道事件句柄()的哪个子class会收到,但我想看看有没有别的办法。在我的实际代码中,我控制并可以更改 PrinterInstances 代码,但我无法更改 printEvent 方法的签名(由其中一个库提供)
*编辑:我认为这与 Provide implicits for all subtypes of sealed type 相同。那里的答案已经将近 2 年了,我想知道它是否仍然是最好的方法?
你必须在某处进行模式匹配。在 Show
实例中执行:
implicit val showEvent = new Show[Event] {
def show(a: Event) = a match {
case SomeEvent(msg) => s"SomeEvent: $msg"
case OtherEvent(code) => s"OtherEvent: $code"
}
}
如果您确实需要 SomeEvent
和 OtherEvent
的单独实例,您可以在不同的对象中提供它们,以便单独导入它们。
如果 Show
被定义为逆变(即 trait Show[-A] { ... }
,泛型上有一个减号)那么一切都可以开箱即用,并且 Show[Event]
可以用作a Show[SomeEvent]
(就此而言,作为 Show[OtherEvent]
)。
如果不幸的是 Show
没有写成逆变的,那么我们可能不得不在我们这边做一些比我们想要的更多的杂耍。我们可以做的一件事是将我们所有的 SomeEvent
值声明为简单的 Event
s,相对于 val fooEvent: Event = SomeEvent("foo")
。那么fooEvent
就可以显示了。
在上述技巧的更极端版本中,我们实际上可以隐藏我们的继承层次结构:
sealed trait Event {
def fold[X]( withSomeEvent: String => X,
withOtherEvent: String => X ): X
}
object Event {
private case class SomeEvent(msg: String) extends Event {
def fold[X]( withSomeEvent: String => X,
withOtherEvent: String => X ): X = withSomeEvent(msg)
}
private case class OtherEvent(code: String) extends Event {
def fold[X]( withSomeEvent: String => X,
withOtherEvent: String => X ): X = withOtherEvent(code)
}
def someEvent(msg: String): Event = SomeEvent(msg)
def otherEvent(code: String): Event = OtherEvent(code)
}
Event.someEvent
和 Event.otherEvent
允许我们构造值,fold
允许我们进行模式匹配。