为密封抽象特征的子类调用 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"
  }
}

如果您确实需要 SomeEventOtherEvent 的单独实例,您可以在不同的对象中提供它们,以便单独导入它们。

如果 Show 被定义为逆变(即 trait Show[-A] { ... },泛型上有一个减号)那么一切都可以开箱即用,并且 Show[Event] 可以用作a Show[SomeEvent](就此而言,作为 Show[OtherEvent])。

如果不幸的是 Show 没有写成逆变的,那么我们可能不得不在我们这边做一些比我们想要的更多的杂耍。我们可以做的一件事是将我们所有的 SomeEvent 值声明为简单的 Events,相对于 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.someEventEvent.otherEvent 允许我们构造值,fold 允许我们进行模式匹配。