【问题标题】:How can I prove that two types have no subtyping relation in Scala?如何证明两种类型在 Scala 中没有子类型关系?
【发布时间】:2015-07-23 08:48:53
【问题描述】:

(注意:这样做的动机需要冗长而困难的解释;您可以在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 都不正确?

【问题讨论】:

    标签: scala types


    【解决方案1】:

    如果您想在编译时严格强制这两种类型不同,那么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
    

    【讨论】:

    • 谢谢!我寻找但显然错过了您提到的问题。不幸的是,我的问题比单纯的隐式解决更棘手,但仅凭含糊的隐式技巧就值得入场:-)
    • 另外,只要您不需要支持通用右手操作数,这里就有一个可行的解决方案:github.com/wix/accord/issues/37#issuecomment-101213060 不幸的是,就我而言,这正是我需要做的。运行时反射解决方案显然也无济于事......
    • @TomerGabel 显然删除第一个隐式就可以了。
    【解决方案2】:

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

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

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

    或:

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

    【讨论】:

    • 虽然有用,但实际上并不能回答最初的问题(如何实现 =!=)。不过,我不知道 DummyImplicit,所以谢谢!
    • 好吧,我认为实际的问题是关于如何“实现二元运算符,使得行为取决于右侧操作数的类型:如果它与左侧相同,则为一种行为操作数,否则有不同的行为”,正如您的示例所示,这可以通过简单的重载来完成(一旦“固定”,即如我上面所示)。从您的描述看来,对工作=!= 操作员的追求仅源于您试图寻找替代重载的方法,因为您无法使其工作。但正如我所展示的,它可以工作。我错过了什么?
    • 这个特殊的问题源于试图在基于 Scala 的 DSL 中解决一个更大的问题;您引用的内容本质上是更具体问题的上下文。但是,重新阅读我的问题,似乎我到底在寻找什么有点模棱两可,所以无论如何你都会得到你的支持:-)
    • 顺便说一句,显然添加任何类型参数化的重载都会破坏我最初希望实现的目标(请参阅问题中链接的 Accord 错误),所以在这一点上我很确定更广泛的问题无法真正解决。
    【解决方案3】:

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

    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 &lt;:&lt; U 的隐式值,因此将使用默认值 @987654326 @ 在这种情况下。

    然而,这只能在运行时工作,所以它可能无法准确回答您的问题,但这个技巧有时可能很有用。

    【讨论】:

    • 只要您正在测试严格的子类型化,需要证明 T<: t>:>U)。
    • 好吧,如果两个参数在运行时都是null,那么使用(implicit ev1: T &lt;:&lt; U = null, ev2: U &lt;:&lt; T = null),则类型不相关。
    • 哦,哇,这是一个巧妙的技巧!太糟糕了,您只能在运行时评估它。这可能可以通过宏来解决,但由于在编译时有一个不同的 hack 可以工作(参见 m-z 链接的stackoverflow.com/questions/6909053/enforce-type-difference),这似乎有点矫枉过正。
    • @TomerGabel,有趣的是,我现在只看了那个答案,似乎这个技巧也可以适用于子类型 - 它使用相同的 null 默认值方法,就像这样一。
    • 有多种选择;到目前为止,我最喜欢的是利用模棱两可的隐含。一百万年都不会想到这一点:-)
    猜你喜欢
    • 2023-03-23
    • 1970-01-01
    • 2021-04-13
    • 1970-01-01
    • 2016-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多