Как правильно использовать неоднозначные следствия для отрицания типов в Scala - PullRequest
3 голосов
/ 23 октября 2019

В конечном счете, я хочу предоставить одну реализацию класса типов для некоторого определенного типа 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 является причиной этой проблемы, но я не могу придумать, как обойти это.

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

1 Ответ

3 голосов
/ 23 октября 2019

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

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

, однако ошибочную реализацию, где мы забываем IntChecker bound

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()
                                               ^
...