Как я могу доказать, что два типа не имеют отношения подтипирования в Scala?

(Примечание: мотивация для этого требует долгого и трудного объяснения, вы можете найти полное обсуждение этой

Первая перегрузка более конкретна, чем вторая, поэтому вы можете ожидать, что вызов, такой как 5 testAgainst 10, запускает первую перегрузку, тогда как 5 testAgainst "abcd" будет ссылаться на вторую перегрузку. Хотя это имеет смысл в теории, это не будет компилироваться, потому что стертая сигнатура одинакова для обеих перегрузок.

Мне удалось обойти это так, чтобы потребовалось добавить параметр типа к первой перегрузке, но это именно то, чего я пытаюсь избежать. Другим решением было бы изменить общую перегрузку, чтобы требовать от компилятора подтверждения того, что между типами нет отношения подтипирования (противоположность =:=, который, к сожалению, не предоставляется библиотекой Scala).

В то время как довольно легко кодировать отношения подтипирования в Scala, я не нашел способа кодировать его отсутствие. Есть ли способ потребовать, чтобы вторая перегрузка была кандидатом во время компиляции, но не T <:< U или T >:> U являются истинными?

Ответ 1

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

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

Ответ 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")

Ответ 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 <:< U, и поэтому будет использоваться значение по умолчанию, которое равно null в этом случае.

Это, однако, будет работать только во время выполнения, поэтому, вероятно, это точно не отвечает на ваш вопрос, но, тем не менее, этот трюк может быть полезен иногда.