Создание списка однотипных объектов

У меня есть черта Animal и несколько классов кейсов, как показано ниже

sealed trait Animal

trait Domestic extends Animal
trait Wild extends Animal

case class Dog(id: UUID = UUID.randomUUID()) extends Domestic
case class Lion(id: UUID = UUID.randomUUID()) extends Wild

Вот мой класс Herd, который может содержать список одного типа животных.

case class Herd[T <: Animal](get: T*)

Я хочу создать стадо из одного вида животных.

val herd1 = Herd(Cat(), Cat())
val herd2 = Herd(Cat(), Lion())

В Scala допустимы и те и другие, но если вы посмотрите на значение слова «стадо кошек и львов», это не имеет смысла. Есть ли способ ограничить Herd одним типом?


person Deepak    schedule 31.05.2021    source источник
comment
Или это нужно смоделировать по-другому?   -  person Deepak    schedule 31.05.2021
comment
Вы можете помочь с Herd[Cat](...) и т. д.?   -  person bobah    schedule 31.05.2021


Ответы (1)


Попробуйте ввести два параметра типа 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