召唤Scala暗示密封的抽象特征

时间:2018-01-20 17:17:37

标签: scala traits implicits subtype

我正在使用两个Scala库,它们都依赖隐式参数来为case类提供编解码器/ 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来抽象底层库的细节 - 调用者应该只需要传递case类,在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()方法中的注释表明我的问题 - 有没有办法让编译器为我选择正确的含义?

我怀疑答案是否定的,因为在编译时编译器不知道哪个事件句柄()的子类将会收到,但我想知道是否有另一种方法。在我的实际代码中,我控制&amp;可以更改PrinterInstances代码,但我无法更改printEvent方法的签名(由其中一个库提供)

*编辑:我认为这与Provide implicits for all subtypes of sealed type相同。答案是将近2年,我想知道它是否仍然是最好的方法?

1 个答案:

答案 0 :(得分: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]可用作{ {1}}(以及此问题的Show[SomeEvent])。

如果遗憾的是Show[OtherEvent]没有被写为逆变,那么我们可能不得不在我们的结尾处做一些比我们想要的更多的玩杂耍。我们可以做的一件事是将所有Show值声明为SomeEvent s,而不是Event。然后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允许我们进行模式匹配。