Обход проверок на дисперсию с помощью методов расширения - PullRequest
5 голосов
/ 01 апреля 2019

Это не компилируется:

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 ]

3 голосов
/ 01 апреля 2019

Я не думаю, что это мошенничество больше, чем версия после удаления сообщений:

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

Причина в том, что параметр типа в конструкторе MyImplicitClass выводится до того, как будет рассмотрен myExtensionMethod.

Изначально я хотел сказать, что это не позволяет вам "обойти все, что проблем, которые нам дают проверки на дисперсию", потому что метод расширения должен быть выражен в терминах методов, разрешающих дисперсию, но это неверно: его можно определить в сопутствующем объекте и использовать приватное состояние.

Единственная проблема, которую я вижу, состоит в том, что это может сбить с толку людей, модифицирующих код (даже не читающих его, поскольку те не увидят некомпилируемый код). Я не ожидал бы, что это будет большая проблема, но без попыток на практике трудно быть уверенным.

2 голосов
/ 01 апреля 2019

Вы не достигли невозможного.Вы просто выбрали компромисс, который отличается от того, что есть в стандартной библиотеке.

То, что вы потеряли

Подпись

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

forceВы должны реализовать свой ковариант Set таким образом, чтобы он работал для Any, потому что B абсолютно не ограничен.Это означает:

  • Нет BitSet с только для Int с
  • Нет Ordering с
  • Нет пользовательских функций хеширования.

Эта подпись заставляет вас по существу реализовать Set[Any].

То, что вы получили

Легко обходимый фасад:

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

компилируется просто отлично.Это означает, что ваш набор x, который был построен как набор целых чисел и поэтому может содержать только целые числа, будет вынужден вызывать метод contains во время выполнения, чтобы определить, содержит ли он String или нет,несмотря на то, что он не может содержать никаких String с.Итак, еще раз, система типов не поможет вам в любом случае устранить такие бессмысленные запросы, которые всегда будут давать false.

...