Я изучаю концепцию дисперсии типов и границ в scala и как их использовать. Я столкнулся с приведенным ниже вопросом о переполнении стека, где в одном из решений упоминалось, как предотвратить обобщение типов scala.
Ниже приведен код, размещенный в решении. В приведенном ниже коде как помогает добавление нового параметра типа C? Я понимаю, как B ограничен (как супертип A и подтип Fruit). Но я совершенно не понимаю, что C здесь делает. Почему он должен быть супертипом A. Почему неявное свидетельство требует, чтобы B был подтипом C?
И почему возникает нерелевантная ошибка при добавлении объекта List of Orange, что Fruit не является подтипом Banana. Может кто-нибудь объяснить это?
Я предполагаю, что для удовлетворения первого ограничения объект Orange выводится как объект Fruit, но после этого теряется, почему он говорит, что Fruit не является подтипом Banana.
case class Banana() extends Fruit
defined class Banana
case class Orange() extends Fruit
defined class Orange
case class Basket[+A <: Fruit](items: List[A]) {
// ...
def addAll[B >: A <: Fruit, C >: A](newItems: List[B])(implicit ev: B <:< C): Basket[B] =
new Basket(items ++ newItems)
// ...
}
defined class Basket
val bananaBasket: Basket[Banana] = Basket(List(Banana(), Banana()))
bananaBasket: Basket[Banana] = Basket(List(Banana(), Banana()))
bananaBasket.addAll(List(Orange())) // not accepted
Main.scala:593: Cannot prove that Product with Serializable with cmd27.Fruit <:< cmd47.Banana.
bananaBasket.addAll(List(Orange()))
Синтаксис
def foo[A >: LA <: UA, B...](...)(implicit ev: F[A, B] <:< G[A, B], ...)
Значит это
A, B ...
следует вывести так, чтобы условия A >: LA <: UA, B...
выполнялись иA, B ...
условий F[A, B] <:< G[A, B], ...
следует проверить.В основном C >: A
и ev: B <:< C
означают (поскольку C
больше нигде не используется, а компилятор ищет нижнюю верхнюю границу для C
), что C
есть A
, и для этого мы должны проверить, что B <:< A
. Просто мы не можем удалить C >: A
и заменить ev: B <:< C
на ev: B <:< A
, так как тогда у нас будет Error: covariant type A occurs in contravariant position in type B <:< A of value ev
.
Итак, мы хотим, чтобы B
выводился таким образом, чтобы B >: A <: Fruit
(т. е. B
должно быть супертипA
), и для этого B
проверялось, что B <:< A
(т. е. B
должно быть подтипA
). Так что это может быть удовлетворено только тогда, когда A = B
. И это предотвращает компиляцию bananaBasket.addAll(List(Orange()))
.
Да, конечно. Виноват. Тогда почему компилятор не настроит C
на Fruit
, что соответствовало бы обоим ограничениям?
@jwvh bananaBasket
это Basket[Banana]
так что A=Banana
. newItems: List[B] = List(Orange())
так B >: Orange
. Таким образом, минимальные B, C
удовлетворяющие [B >: A <: Fruit, C >: A]
есть B=Fruit, C=A=Banana
, а это не удовлетворяет ev: B <:< C
. Ошибка компиляции говорит именно об этом: Error: Cannot prove that App.Fruit <:< App.Banana
. (Я сделал trait Fruit extends Product with Serializable
, чтобы избежать 1).
@jwvh И вывод типа, и неявное разрешение происходят во время компиляции, но A,B...
в [A >: LA <: UA, B...]
выводятся перед проверкой (implicit ev: F[A, B] <:< G[A, B], ...)
. См. примеры def tupleIfSubtype[T <: U, U]
и def tupleIfSubtype[T, U](t: T, u: U)(implicit ev: T <:< U)
в blog.bruchez.name/2015/11/….
@jwvh Это были примеры, когда типы выводятся до разрешения неявных. А вот пример наоборот, когда неявное разрешение разрешается до того, как выводится тип: trait Typeclass[T]
implicit val int: Typeclass[Int] = null
def foo[T]()(implicit ev: Typeclass[T]) = ???
foo()
@dmytromitin Спасибо за объяснение. Теперь мне ясно. Не могли бы вы также объяснить, почему вы использовали F[A, B]
и G[A, B]
в неявном ev? Как мне это прочитать и что они означают? Разве B <:< A
не является представлением <:<[B, A]
?
@Aandal Да, B <:< A
это <:<[B, A]
. Я имел в виду, что в implicit ev
можно проверить, что какой-то тип, зависящий от A, B
(например, это может быть B
), является подтипом какого-то другого типа, зависящего от A,B
(например, это может быть A
).
@jwvh Нет, неявные значения также разрешаются во время компиляции.