Попробуйте ввести два параметра типа A
и B
, а затем связать их с ограничением обобщенного типа A =:= B
case class Herd[A <: Animal, B](x: A, y: B*)(implicit ev: A =:= B)
Herd(Lion()) // ok
Herd(Lion(), Lion()) // ok
Herd(Cat(), Lion()) // compile-time error
что именно =: =
Рассмотрим следующий метод с двумя параметрами типа A
и B
, где мы стремимся показать, что они должны быть равны или, по крайней мере, A
должны быть подтипом B
scala> def f[A, B](a: A, b: B): B = {
| val x: B = a
| x
| }
val x: B = a
^
On line 2: error: type mismatch;
found : a.type (with underlying type A)
required: B
Два параметра типа совершенно не связаны в приведенном выше определении, и тело метода не может влиять на вывод типа параметров типа, поэтому он вызывает ошибку. Теперь давайте попробуем связать их с привязкой к типу A <: B
scala> def f[A <: B, B](a: A, b: B): B = {
| val x: B = a
| x
| }
def f[A <: B, B](a: A, b: B): B
Таким образом, это компилируется, однако компилятор всегда будет пытаться удовлетворить ограничения типа, вычисляя наименьшую верхнюю границу данных аргументов.
scala> f(Lion(), Dog())
val res32: Product with Animal with java.io.Serializable = Lion(...)
Нам нужно нечто большее, чтобы обойти тенденцию компилятора определять наименьшую верхнюю границу, и именно здесь в игру вступает ограничение обобщенного равенства типов.
scala> def f[A <: B, B](a: A, b: B)(implicit ev: A =:= B): B = {
| val x: B = a
| x
| }
def f[A <: B, B](a: A, b: B)(implicit ev: A =:= B): B
scala> f(Lion(), Cat())
^
error: Cannot prove that Lion =:= Product with Animal with java.io.Serializable.
Теперь компилятор по-прежнему должен попытаться сгенерировать наименьшую верхнюю границу заданных аргументов, однако он также должен удовлетворить дополнительное требование - иметь возможность генерировать свидетель ev
для двух одинаковых типов A
и B
. (Обратите внимание, что свидетель ev
будет автоматически создан компилятором, если это возможно.)
Когда у нас есть свидетель ev
, мы можем свободно перемещаться между типами A
и B
с помощью его apply
метода, например, рассмотрим
scala> type A = Lion
type A
scala> type B = Lion
type B
scala> val a: A = Lion()
val a: A = Lion(...)
scala> val ev: =:=[A, B] = implicitly[A =:= B]
val ev: A =:= B = generalized constraint
scala> ev.apply(a)
val res44: B = Lion(...)
Обратите внимание, как ev.apply(a)
набирает B
. Причина, по которой мы можем применить =:=
таким образом, заключается в том, что на самом деле это функция
scala> implicitly[(A =:= B) <:< Function1[A, B]]
val res43: A =:= B <:< A => B = generalized constraint
поэтому неявный список параметров
(implicit ev: A =:= B)
по сути, определяет неявную функцию преобразования
(implicit ev: A => B)
поэтому теперь компилятор может автоматически вводить неявное преобразование везде, где это необходимо, поэтому следующие
def f[A <: B, B](a: A, b: B)(implicit ev: A =:= B): B = {
val x: B = a
x
}
автоматически расширяется до
def f[A <: B, B](a: A, b: B)(implicit ev: A =:= B): B = {
val x: B = ev.apply(a)
x
}
Таким образом, как и границы типов, ограничения обобщенного типа - это дополнительный способ попросить компилятор выполнить дальнейшие проверки нашей кодовой базы во время компиляции.
person
Mario Galic
schedule
31.05.2021
Herd[Cat](...)
и т. д.? - person bobah   schedule 31.05.2021