如何结合两个消费者?

时间:2016-01-04 19:15:12

标签: scala

给定消耗类型为T的值的简单特征:

trait Consumer[-T] extends (T => Unit) {
  def +[U, P](other: Consumer[U]): Consumer[P] = ???
}

如何实现将两个使用者组合成一个接受AnyRef的有效超类型或AnyVal的更宽类型的+函数?对于以下情况:

-AnyRefs具有常见的超类型:

trait Base
trait A extends Base
trait B extends Base

val c1: Consumer[A] = _
val c2: Consumer[B] = _
val cc = c1 + c2 //cc must have type of Consumer[Base]

-AnyVals:

val c1: Consumer[Int] = _
val c2: Consumer[Long] = _
val cc = c1 + c2 //cc must have type of Consumer[Long]

2 个答案:

答案 0 :(得分:3)

您正在尝试实现部分功能的概念。这样做时,您需要意识到您为了动态而牺牲了类型安全性。在强类型语言中,如果没有明确的黑客攻击,通常不会这样做,例如:类型转换或差异规则缓解。

例如,标准Scala的PartialFunction为您执行此操作:它允许将函数分配给另一个禁用参数逆变检查的函数。例如,具有类型层次结构

trait Animal
case class Dog() extends Animal
case class Cat() extends Animal

,它允许将Dog => T类型的函数“赋值”给类型为Animal => T的函数(与逆变规则相矛盾):

val pf: PartialFunction[Animal, Unit] = {
  case Dog() => println("dog")
}

以牺牲运行时现在可能的匹配错误为代价:

pf(Cat()) // MatchError

因此,通常在Scala中组合多个使用者时,您可以在定义部分函数时执行与Scala语言完全相同的技巧 - 将使用者参数方差更改为协变,即-T+T。为此,请使用@uncheckedVariance注释:

trait Consumer[+T, +V] extends (T@uncheckedVariance ⇒ V) {
  def handle(command: T@uncheckedVariance): V
}

这种差异允许有一个消费者的查找表:

val lookup: Map[Class[_], Consumer[Animal]] = Map(
  classOf[Dog] → dogConsumer,
  classOf[Cat] → catConsumer
)

可用于基于(在此示例中)类类型实现调度。

答案 1 :(得分:1)

这是不可能的,因为你需要保证超类型只包含 你提到的子类型,否则你将会不健全:

trait A
trait B extends A
trait C extends A
trait D extends A

val x: Consumer[A] = (new Consumer[B]) + (new Consumer[C])
x(new D)   // Uh-oh, nobody knows how to handle this!

随着对宏的充分尝试,你可能会让它发挥作用,但我建议你首先询问你想要完成什么,看看是否有一个不那么脆弱的设计也能实现它。

例如,如果你真的不关心是否确实会消耗某些东西,并且你只是使用这些类型来避免明显的废话,你可以做类似的事情

abstract class Consumer[A: reflect.ClassTag] {
  def apply(a: A): Unit
  def consume(a: Any) {
    val c = implicitly[reflect.ClassTag[A]].runtimeClass
    if (c isInstance a) apply((c cast a).asInstanceOf[A])
  }
  def +[A1 >: A : reflect.ClassTag](that: Consumer[A1]) = {
    new Consumer[A1] {
      def apply(a1: A1) = { consume(a1); that.consume(a1) }
    }
  }
}