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

时间:2015-05-12 12:55:58

标签: scala types

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

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

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都不是真的?

3 个答案:

答案 0 :(得分:3)

如果要在编译时强制执行两种类型的不同,那么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

答案 1 :(得分:3)

解决方案实际上非常简单。你唯一真正的问题是你的重载都有相同的擦除,这只是编译器的一个问题,因为底层JVM的局限性。就打字而言,拥有这两个重载是完全没问题的。

所以你要做的就是以一种功能相同的方式改变一个重载的签名。这可以使用始终可以找到的隐式参数(通常是标准库的DummyImplicit)或通过添加具有默认值的虚拟参数来完成。所以其中任何一个都没问题(我通常使用第一个版本):

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

或:

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

答案 2 :(得分:0)

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

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

然而,这只能在运行时工作,所以它可能不会完全回答你的问题,但是这个技巧有时可能会有用。