Параметр типа ковариации с несколькими ограничениями

Я изучаю концепцию дисперсии типов и границ в 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()))
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
149
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Синтаксис

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

@jwvh Нет, неявные значения также разрешаются во время компиляции.

Dmytro Mitin 06.05.2019 11:26

Да, конечно. Виноват. Тогда почему компилятор не настроит C на Fruit, что соответствовало бы обоим ограничениям?

jwvh 06.05.2019 11:30

@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).

Dmytro Mitin 06.05.2019 11:49

@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/….

Dmytro Mitin 06.05.2019 11:58

@jwvh Это были примеры, когда типы выводятся до разрешения неявных. А вот пример наоборот, когда неявное разрешение разрешается до того, как выводится тип: trait Typeclass[T]implicit val int: Typeclass[Int] = nulldef foo[T]()(implicit ev: Typeclass[T]) = ???foo()

Dmytro Mitin 06.05.2019 12:04

@dmytromitin Спасибо за объяснение. Теперь мне ясно. Не могли бы вы также объяснить, почему вы использовали F[A, B] и G[A, B] в неявном ev? Как мне это прочитать и что они означают? Разве B <:< A не является представлением <:<[B, A]?

Aandal 07.05.2019 07:04

@Aandal Да, B <:< A это <:<[B, A]. Я имел в виду, что в implicit ev можно проверить, что какой-то тип, зависящий от A, B (например, это может быть B), является подтипом какого-то другого типа, зависящего от A,B (например, это может быть A).

Dmytro Mitin 07.05.2019 09:53

Другие вопросы по теме