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

时间:2019-04-01 12:57:53

标签: scala covariance generic-variance

无法编译:

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的协变“集合”。但是,如果这不可能,那么编译器是否应该禁止它?

2 个答案:

答案 0 :(得分:3)

我不认为欺骗后的版本比它欺骗的更多:

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

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

最初,我想说的是它不会让您“规避方差检查给我们带来的任何问题”,因为扩展方法需要以方差合法方法表示,但是这是错误的:可以在伴随对象中定义它并使用私有状态。

我看到的唯一问题是,人们修改代码可能会造成混乱(甚至不阅读代码,因为那些人​​不会看到未编译的代码)。我不希望这是一个问题,但是如果没有实践尝试,很难确定。

答案 1 :(得分:2)

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

您丢失了什么

签名

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

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

  • 没有BitSet的{​​{1}}个
  • 没有Int
  • 没有自定义哈希函数。

此签名迫使您实质上实现Ordering

您获得的成就

易于绕开的门面:

Set[Any]

编译就可以了。这意味着您的val x: CovariantSet[Int] = ??? (x: CovariantSet[Any]).contains("stuff it cannot possibly contain") 集已经被构造为一组整数,因此只能包含整数,将被迫在运行时调用方法x以确定其是否包含{{是否包含1}},尽管事实可能不包含任何contains。同样,类型系统并不能以任何方式帮助您消除此类无意义的查询,这些查询总是会产生String