给定消耗类型为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]
答案 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) }
}
}
}