#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]
метод withappend(x: A)
, вы всегда можете вызвать этот метод со значениемy: B
ifB <: A
. Это разумно по определению, потому что этот метод видитy
как значение типаA
и может безопасно обращаться с ним так, потомуB
что имеет каждую функцию (элемент), котораяA
имеет.4. Вы не можете писать
val q1: Queue[Fruit] = new Queue[Apple](List(new Apple), Nil)
без аннотации дисперсии, потому что теперь aQueue[Apple]
больше не является подтипом ofQueue[Fruit]
. Вы можете построить aval q1: Queue[Fruit] = new Queue[Fruit](List(new Apple), Nil)
, потомуList[ A]
что он ковариантен по своему типу элемента, так что aList[Apple] <: List[Fruit]
. Таким образом, Scala правильно определит ожидаемый возвращаемый типQueue[Fruit]
.5. Как правило, используйте аннотации различий, если это позволяет использование вашего параметра типа. Scala — это язык с подтипом и предпочтением неизменяемых типов данных, использование аннотаций различий делает его использование плавным. Не беспокойтесь, что возвращаемый тип может быть «расширен», например, до
Queue[Any]
, потому что на следующем шаге вы увидите, что вы, вероятно, ничего не будете делать с таким типом.