我如何证明两种类型在 Scala 中没有子类型关系?

How can I prove that two types have no subtyping relation in Scala?

(注意:这个动机需要一个漫长而艰难的解释;你可以找到关于这个 Accord issue 的完整讨论。它甚至可能不正确问题的解决方案,但我相信这个问题本身很有趣。)

我正在寻找一种实现二元运算符的方法,使得行为取决于右侧操作数的 type:如果它与左侧操作数,否则行为不同。例如:

implicit class Extend[T](lhs: T) {
  def testAgainst(rhs: T) = println("same type")
  def testAgainst[U](rhs: U) = println("different type")
}

第一个重载比第二个更具体,因此您会期望 5 testAgainst 10 之类的调用触发第一个重载,而 5 testAgainst "abcd" 会调用第二个重载。虽然这在理论上是有意义的,但这不会编译,因为擦除的签名对于两个重载都是相同的。

我设法以一种需要向第一个重载添加类型参数的方式解决这个问题,但这正是我试图避免的。一个不同的解决方案是修改泛型重载以要求编译器证明类型之间没有子类型关系(与 =:= 相反,不幸的是 Scala 库没有提供)。

虽然在 Scala 中对子类型关系进行编码通常很容易,但我发现没有办法对其中的缺乏进行编码。有什么方法可以要求,为了使第二个重载成为编译时的候选者,T <:< UT >:> U 都不为真?

如果你想在编译时强制两种类型严格不同,那么 this is the question 适合你。使用定义 =!= 的答案之一,我们可以想象多种方法如下所示:

implicit class Extend[T](lhs: T) {
  def testAgainst(rhs: T) = println("same type")
  def testAgainst[U](rhs: U)(implicit ev: T =!= U) = println("different type")
}

我们也可以很容易地使用 TypeTag 在一种方法中进行类型测试。

import scala.reflect.runtime.universe._

implicit class Extend[T: TypeTag](lhs: T) {
  def testAgainst[U: TypeTag](rhs: U): Boolean = typeOf[T] =:= typeOf[U]
}

您当然可以修改它以分支行为。

scala> 1 testAgainst 2
res98: Boolean = true

scala> 1 testAgainst "a"
res99: Boolean = false

scala> List(1, 2, 3) testAgainst List(true, false)
res100: Boolean = false

scala> List(1, 2) testAgainst List.empty[Int]
res102: Boolean = true

解决方案实际上非常简单。您唯一真正的问题是您的两个重载具有相同的擦除,由于底层 JVM 的限制,这对编译器来说只是一个问题。就打字而言,拥有这两个重载非常好。

因此,您所要做的就是以一种功能等效的方式更改一个重载的签名。这可以使用一个总是可以找到的隐式参数(通常是标准库的 DummyImplicit)或通过添加一个具有默认值的虚拟参数来完成。所以这两个都可以(我通常使用第一个版本):

def testAgainst[U](rhs: U)(implicit dummy: DummyImplicit) = println("different type")

或:

def testAgainst[U](rhs: U, dummy: Int = 0) = println("different type")

还有一种方法可以在运行时根据隐式解析规则和默认隐式值确定类型之间不存在子类型关系。可以通过这个简单的函数及其调用来说明:

scala> def checkSubtypes[T, U](implicit ev: T <:< U = null) = ev
checkSubtypes: [T, U](implicit ev: <:<[T,U])<:<[T,U]

scala> checkSubtypes[Int, Long]
res4: <:<[Int,Long] = null

scala> checkSubtypes[Integer, Number]
res5: <:<[Integer,Number] = <function1>

如果类型 T 不是其他类型 U 的子类型,编译器将无法找到 T <:< U 的隐式值,因此默认值将被使用,在本例中为 null

然而,这只会在运行时起作用,因此它可能无法准确回答您的问题,但是这个技巧有时可能会有用。