【问题标题】:Summoning Scala implicits for subclasses of sealed abstract trait为密封抽象特征的子类调用 Scala 隐式
【发布时间】:2018-01-20 17:17:37
【问题描述】:

我正在使用两个 Scala 库,它们都依赖隐式参数来为案例类提供编解码器/编组器(有问题的库是 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 - 调用者应该只需要传递案例类,在 API 实现内部应该调用相关的隐式。

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() 方法中的 cmets 表明了我的问题 - 有没有办法让编译器为我选择正确的隐式?

我怀疑答案是否定的,因为在编译时编译器不知道 Event handle() 的 哪个 子类将接收,但我想看看是否还有其他方法。在我的实际代码中,我控制 & 可以更改 PrinterInstances 代码,但我无法更改 printEvent 方法的签名(由其中一个库提供)

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

【问题讨论】:

  • 蛮力方法:用单个implicit def somePrinter: Printer[Event] 替换多态implicit def somePrinter 并在内部处理案例。
  • 我们的情况并不像Provide implicits for all subtypes of sealed type那么绝望。特别是,我们不需要 'shapeless' 的原始力量 :-)

标签: scala traits implicits subtype


【解决方案1】:

您必须在某处进行模式匹配。在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] 可以用作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 允许我们进行模式匹配。

【讨论】:

  • 谢谢,我最终使用了第一个建议的方法。不幸的是,我需要实现的实际特征既有一个方法(采用类型参数的实例)和一个需要重写的 val,所以我无法让它像 Show[Event] 一样工作上面的例子。我必须将我的事件显式传递给一个方法,该方法根据参数选择正确的具体编解码器实例,然后将结果分配给隐式。但它仍然符合向调用者隐藏编解码器/编组细节的标准,这很棒。
猜你喜欢
  • 2015-05-26
  • 2011-03-03
  • 2016-03-29
  • 2017-09-08
  • 2018-12-18
  • 2021-10-21
  • 1970-01-01
  • 2015-09-17
  • 2016-05-28
相关资源
最近更新 更多