нижняя и верхняя границы scala

#scala #generics

#scala #дженерики

Вопрос:

 class Queue[ T](
    private val leading: List[T],
    private val trailing: List[T]
) {
    def append[U >: T](x: U) =
      new Queue[U](leading, x :: trailing) // ...
}

class Fruit

class oranges extends Fruit

class apple extends Fruit

class diffAppale

val q1: Queue[Fruit] = new Queue[apple](List(new apple), List())
//> q1  : Test.Queue[Test.Fruit] = Test$Queue@30c7da1e

q1.append(new Fruit)                            
//> res0: Test.Queue[Test.Fruit] = Test$Queue@506e6d5e

q1.append(new oranges)                          
//> res1: Test.Queue[Test.Fruit] = Test$Queue@96532d6

q1.append(new diffAppale)  // i want to restrict this             
//> res2: Test.Queue[Object] = Test$Queue@3796751b
 

Здесь я мог бы добавить к функции добавления любой объект, который я могу, я вижу, что результирующий тип понижен до наименьшего общего знаменателя

Но я хотел бы иметь такое же поведение, как у java, скажем, def append[? super T](x: U) // здесь функция добавления будет принимать все объекты, которые являются супертипом T , как я могу добиться аналогичного в scala (реализовать super и расширить для дженериков, таких как java)

Комментарии:

1. Почему это проблема для вас?

2. Я хочу добавить некоторые конкретные типы объектов только через функцию добавления, которой можно управлять >:, <: .. Допустимо ли это?

3. Я думаю, вы пытаетесь думать сложнее, чем нужно. Если ваш требуемый возвращаемый тип в конкретном случае равен Queue[Fruit] , он в любом случае не позволит вам вернуться Queue[Any] . В большинстве случаев не имеет смысла искусственно вводить дополнительные ограничения типа, когда программа правильно проверяет тип. В любом случае, как я предлагаю в ответе, если вы действительно этого хотите, просто удалите дисперсию.

Ответ №1:

Я не понимаю, почему вы хотите ограничить это или почему возврат более общего нового неизменяемого объекта создает для вас проблему.

Но если вам не нужна эта дисперсия, просто удалите все аннотации к дисперсии:

 class Queue[A](leading: List[A], trailing: List[A]) {
  def append(x: A) = new Queue[A](leading, x :: trailing) // ...
}

class Fruit
class Orange extends Fruit
class Apple  extends Fruit
class NotFruit

val q1: Queue[Fruit] = new Queue(List(new Apple), Nil)
val q2 = q1.append(new Fruit)  // ok
val q3 = q2.append(new Orange) // ok

q1.append(new NotFruit) // error - found NotFruit, required Fruit
 

Что касается вопроса:

функция добавления будет принимать все объекты, которые являются супертипом T

Это то, что уже делает ваш исходный код. Обратите внимание, что Any это супертип всех типов. Поскольку аргумент находится в ковариантной позиции, всегда можно передать значение с подтипом Any для аргумента ожидаемого типа Any . Это жизнь 🙂 Scala просто отличается от Java, потому что она построена на различии сайтов объявлений и не использует различия сайтов (возможно, лучшее решение, чем дженерики Java).

Если вам нужны дополнительные ограничения, вы можете запросить параметр доказательства, например

 def append[B >: A](x: B)(implicit ev: B <:< Seedless): Queue[B]
 

Или поставьте верхнюю границу (это не может быть таким же, как A из A -за дисперсии):

 def append[B >: A <: Fruit](x: B): Queue[B]
 

Но: имхо, это действительно не имеет смысла.

Комментарии:

1. Теперь я немного смущен :), Вы удалили покрытие A( A), но все же вы можете добавить очередь [яблоки] в очередь [Фрукты], как показано ниже

2. значение q1: очередь [Фрукты] = новая очередь (список (новое яблоко), ноль) Но я не смог бы добавить, как показано ниже, если удалить coverience val q1: Очередь [Фрукты] = новая очередь [яблоко] (Список (новое яблоко), Список ()), в чем разница!!!

3. @sharathchandra Scala (как и Java) — это язык с подтипом. Параметр типа A всегда может быть удовлетворен путем передачи значения типа B <: A . Для этого вам не нужны аннотации к дисперсии, это, так сказать, «встроенная» функция. Таким образом, если у вас есть Queue[A] метод with append(x: A) , вы всегда можете вызвать этот метод со значением y: B if B <: A . Это разумно по определению, потому что этот метод видит y как значение типа A и может безопасно обращаться с ним так, потому B что имеет каждую функцию (элемент), которая A имеет.

4. Вы не можете писать val q1: Queue[Fruit] = new Queue[Apple](List(new Apple), Nil) без аннотации дисперсии, потому что теперь a Queue[Apple] больше не является подтипом of Queue[Fruit] . Вы можете построить a val q1: Queue[Fruit] = new Queue[Fruit](List(new Apple), Nil) , потому List[ A] что он ковариантен по своему типу элемента, так что a List[Apple] <: List[Fruit] . Таким образом, Scala правильно определит ожидаемый возвращаемый тип Queue[Fruit] .

5. Как правило, используйте аннотации различий, если это позволяет использование вашего параметра типа. Scala — это язык с подтипом и предпочтением неизменяемых типов данных, использование аннотаций различий делает его использование плавным. Не беспокойтесь, что возвращаемый тип может быть «расширен», например, до Queue[Any] , потому что на следующем шаге вы увидите, что вы, вероятно, ничего не будете делать с таким типом.