使用扩展方法规避差异检查



这不会编译:

class MyClass[+A] {
def myMethod(a: A): A = a
}
//error: covariant type A occurs in contravariant position in type A of value a

好吧,很公平。但这确实编译:

class MyClass[+A]
implicit class MyImplicitClass[A](mc: MyClass[A]) {
def myMethod(a: A): A = a
}

这使我们能够规避差异检查给我们带来的任何问题:

class MyClass[+A] {
def myMethod[B >: A](b: B): B = b  //B >: A => B
}
implicit class MyImplicitClass[A](mc: MyClass[A]) {
def myExtensionMethod(a: A): A = mc.myMethod(a)  //A => A!!
}
val foo = new MyClass[String]
//foo: MyClass[String] = MyClass@4c273e6c
foo.myExtensionMethod("Welp.")
//res0: String = Welp.
foo.myExtensionMethod(new Object())
//error: type mismatch

这感觉就像作弊。应该避免吗?或者编译器让它滑动是否有一些合法的理由?

更新:

例如,考虑一下:

class CovariantSet[+A] {
private def contains_[B >: A](b: B): Boolean = ???
}
object CovariantSet {
implicit class ImpCovSet[A](cs: CovariantSet[A]) {
def contains(a: A): Boolean = cs.contains_(a)
}
}

当然,我们似乎已经设法实现了不可能的目标:一个仍然满足A => Boolean的协变"集合"。但是,如果这是不可能的,编译器不应该禁止它吗?

我不认为它比脱糖后的版本更作弊:

val foo: MyClass[String] = ...
new MyImplicitClass(foo).myExtensionMethod("Welp.") // compiles
new MyImplicitClass(foo).myExtensionMethod(new Object()) // doesn't

原因是构造函数上的类型参数MyImplicitClass在考虑myExtensionMethod之前被推断出来。

最初我想说的是,它不能让你"规避方差检查给我们带来的任何问题",因为扩展方法需要用方差法律方法来表达,但这是错误的:它可以在配套对象中定义并使用私有状态。

我看到的唯一问题是,修改代码的人可能会感到困惑(甚至不阅读它,因为那些人不会看到非编译代码)。我不希望这是一个问题,但如果不在实践中尝试,很难确定。

你没有实现不可能的事情。您只是选择了与标准库中不同的权衡。

你失去了什么

签名

def contains[B >: A](b: B): Boolean

强制你以适用于Any的方式实现协变Set,因为B是完全不受约束的。这意味着:

  • Int没有BitSet
  • 没有Orderings
  • 没有自定义哈希函数。

此签名迫使您基本上实现一个Set[Any]

你收获了什么

易于规避的立面:

val x: CovariantSet[Int] = ???
(x: CovariantSet[Any]).contains("stuff it cannot possibly contain")

编译就好了。这意味着您的集合x(已构造为一组整数,因此只能包含整数)将被迫在运行时调用contains的方法以确定它是否包含String,尽管它不可能包含任何Strings。所以再一次,类型系统不会以任何方式帮助你消除这种无意义的查询,这些查询总是会产生false

相关内容

  • 没有找到相关文章

最新更新