Как правильно использовать неоднозначные имплициты для отрицания типа в Scala

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

Вот как определяется привязка контекста:

scala> trait NotAnInt[A]
defined trait NotAnInt

scala> implicit def everythingIsNotAnInt[A]: NotAnInt[A] = new NotAnInt[A] {}
everythingIsNotAnInt: [A]=> NotAnInt[A]

scala> implicit def intsAreInts1: NotAnInt[Int] = ???
intsAreInts1: NotAnInt[Int]

scala> implicit def intsAreInts2: NotAnInt[Int] = ???
intsAreInts2: NotAnInt[Int]

scala> implicit def nothingsAreInts1: NotAnInt[Nothing] = ???
nothingsAreInts1: NotAnInt[Nothing]

scala> implicit def nothingsAreInts2: NotAnInt[Nothing] = ???
nothingsAreInts2: NotAnInt[Nothing]

На данный момент NotAnInt[T] вызывается для всех T, кроме Int/Nothing:

scala> implicitly[NotAnInt[String]]
res3: NotAnInt[String] = $anon$1@1a24fe09

scala> implicitly[NotAnInt[Int]]
<console>:16: error: ambiguous implicit values:
 both method intsAreInts1 of type => NotAnInt[Int]
 and method intsAreInts2 of type => NotAnInt[Int]
 match expected type NotAnInt[Int]
       implicitly[NotAnInt[Int]]
                 ^

scala> implicitly[NotAnInt[Nothing]]
<console>:18: error: ambiguous implicit values:
 both method nothingsAreInts1 of type => NotAnInt[Nothing]
 and method nothingsAreInts2 of type => NotAnInt[Nothing]
 match expected type NotAnInt[Nothing]
       implicitly[NotAnInt[Nothing]]
                 ^

Теперь у меня определена привязка контекста NotAnInt, и я могу создать свой класс типов с его реализациями:

scala> trait IntChecker[A] { def isInt(): Boolean }
defined trait IntChecker

scala> implicit val intIntChecker: IntChecker[Int] = new IntChecker[Int] { override def isInt = true }
intIntChecker: IntChecker[Int] = $anon$1@585dd35c

scala> implicit def otherIntChecker[A: NotAnInt]: IntChecker[A] = new IntChecker[A] { override def isInt = false }
otherIntChecker: [A](implicit evidence$1: NotAnInt[A])IntChecker[A]

Этот класс типа можно использовать, как и ожидалось:

scala> def printIntStatus[T: IntChecker](t: T): Unit = { println(implicitly[IntChecker[T]].isInt()) }
printIntStatus: [T](t: T)(implicit evidence$1: IntChecker[T])Unit

scala> printIntStatus(3)
true

scala> printIntStatus("three")
false

Однако также компилируется следующее:

scala> def printIntStatusWithBug[T](t: T): Unit = { println(implicitly[IntChecker[T]].isInt()) }
printIntStatusWithBug: [T](t: T)Unit

scala> printIntStatusWithBug(3)
false

scala> printIntStatusWithBug("three")
false

Я бы не ожидал, что эта вторая функция скомпилируется, поскольку не должно быть неявного IntChecker[T]. Я ожидаю, что everythingIsNotAnInt является причиной этой проблемы, но я не могу придумать, как это обойти.

Меня интересует, почему этот подход терпит неудачу, а также альтернативные методы достижения того же самого. Спасибо.


person William Carter    schedule 22.10.2019    source источник


Ответы (1)


Рассмотрим следующую альтернативную реализацию (в которой используются неравенства типов Сабина)

trait =!=[A, B]
implicit def neq[A, B] : A =!= B = null
implicit def neqAmbig1[A] : A =!= A = null
implicit def neqAmbig2[A] : A =!= A = null

trait IntChecker[A] {
  def isInt(): Boolean
}

object IntChecker {
  import scala.reflect.ClassTag
  implicit val intIntChecker: IntChecker[Int] = () => true
  implicit def notIntIntChecker[T: ClassTag](implicit ev: T =!= Int): IntChecker[T] = () => false
}

def printIntStatus[T: IntChecker](t: T) = implicitly[IntChecker[T]].isInt()

import IntChecker._
printIntStatus(3)
printIntStatus("three")

который выводит

res0: Boolean = true
res1: Boolean = false

однако глючная реализация, где мы забываем IntCheckerbound

def printIntStatusWithBug[T](t: T) = implicitly[IntChecker[T]].isInt()

не должен компилироваться из-за привязки T: ClassTag в

implicit def notIntIntChecker[T: ClassTag](implicit ev: T =!= Int)

выдает ошибку компилятора

could not find implicit value for parameter e: IntChecker[T]
def printIntStatusWithBug[T](t: T) = implicitly[IntChecker[T]].isInt()
                                               ^
person Mario Galic    schedule 22.10.2019
comment
Фантастическое решение. Я добавил ClassTag к моему творению NotAnInt (implicit def everythingIsNotAnInt[A: ClassTag]: NotAnInt[A] = new NotAnInt[A] {}), чтобы исправить это в одну строку. Благодарю вас! - person William Carter; 23.10.2019