scalabook

Форк
0
/
types-intersection.md 
223 строки · 8.6 Кб

Пересечение типов

Используемый для типов оператор &

создает так называемый тип пересечения (intersection type). Тип A & B
представляет собой значения, которые одновременно относятся как к типу A
, так и к типу B
. Например, в следующем примере используется тип пересечения Resettable & Growable[String]
:

trait Resettable:
def reset(): Unit
trait Growable[A]:
def add(a: A): Unit
def f(x: Resettable & Growable[String]): Unit =
x.reset()
x.add("first")

В методе f

в этом примере параметр x
должен быть как Resettable
, так и Growable[String]
.

Все члены типа пересечения A

и B
являются типом A
и типом B
. Следовательно, как показано, для Resettable & Growable[String]
доступны методы reset
и add
.

Пересечение типов может быть полезно для структурного описания требований. В примере выше для f

мы прямо заявляем, что нас устраивает любое значение для x
, если оно является подтипом как Resettable
, так и Growable
. Нет необходимости создавать номинальный вспомогательный trait, подобный следующему:

trait Both[A] extends Resettable, Growable[A]
def f(x: Both[String]): Unit

Существует важное различие между двумя вариантами определения f

: в то время как оба позволяют вызывать f
с экземплярами Both
, только первый позволяет передавать экземпляры, которые являются подтипами Resettable
и Growable[String]
, но не Both[String]
.

Обратите внимание, что &

коммутативно: A & B
имеет тот же тип, что и B & A
.

Общие элементы

Если элемент присутствует и в A

, и в B
, его тип в A & B
является пересечением типа A
и типа B
. Например, рассмотрим:

trait A:
def children: List[A]
trait B:
def children: List[B]
val x: A & B = new C
val ys: List[A & B] = x.children

Тип children

в A & B
является пересечением типа children
в A
и его типа в B
, то есть List[A] & List[B]
. Это может быть дополнительно упрощено до List[A & B]
, потому что List
является ковариантным.

При определении класса C

, который наследует A
и B
, необходимо дать определение метода children
с требуемым типом:

class C extends A, B:
def children: List[A & B] = ???

Пример

trait Book:
def title: String
trait Audio:
def track: String
case class AudioBook(title: String, track: String) extends Book, Audio
case class BookAudio(title: String, track: String) extends Audio, Book
trait Library:
val first: Book
def items: List[Book]
trait Album:
val first: Audio
def items: List[Audio]
class AudiobookLibrary(titles: String*) extends Library, Album:
private val title: String = titles.head
val first: Book & Audio = AudioBook(title, "empty")
def items: List[Book & Audio] =
titles.map(title => BookAudio("empty", title)).toList
val al = AudiobookLibrary("Пушкин", "Толстой", "Достоевский")
val book: Book = al.first
// book: Book = AudioBook(title = "Пушкин", track = "empty")
println(book.title)
// Пушкин
val books: List[Book] = al.items
// books: List[Book] = List(
// BookAudio(title = "empty", track = "Пушкин"),
// BookAudio(title = "empty", track = "Толстой"),
// BookAudio(title = "empty", track = "Достоевский")
// )
books.foreach(b => println(b.title))
// empty
// empty
// empty
val audio: Audio = al.first
// audio: Audio = AudioBook(title = "Пушкин", track = "empty")
println(audio.track)
// empty
val audios: List[Audio] = al.items
// audios: List[Audio] = List(
// BookAudio(title = "empty", track = "Пушкин"),
// BookAudio(title = "empty", track = "Толстой"),
// BookAudio(title = "empty", track = "Достоевский")
// )
audios.foreach(a => println(a.track))
// Пушкин
// Толстой
// Достоевский

Детали

Правила подтипа

T <: A    T <: B
----------------
  T <: A & B

    A <: T
----------------
    A & B <: T

    B <: T
----------------
    A & B <: T

Из приведенных выше правил можно показать, что &

коммутативно: A & B <: B & A
для любых A
и B
.

B <: B           A <: A
----------       -----------
A & B <: B       A & B <: A
---------------------------
       A & B  <:  B & A

Другими словами, A & B

- это тот же тип, что и B & A
, в том смысле, что эти два типа имеют одинаковые значения и являются подтипами друг друга.

Если C

- конструктор типа, то C[A] & C[B]
можно упростить, используя следующие три правила:

  • если C
    ковариантно, C[A] & C[B] ~> C[A & B]
  • если C
    контравариантно, C[A] & C[B] ~> C[A | B]
  • если C
    невариантно, выдается ошибка компиляции

Когда C

является ковариантным, C[A & B] <: C[A] & C[B]
можно вывести:

    A <: A                  B <: B
  ----------               ---------
  A & B <: A               A & B <: B
---------------         -----------------
C[A & B] <: C[A]          C[A & B] <: C[B]
------------------------------------------
      C[A & B] <: C[A] & C[B]

Когда C

контравариантно, C[A | B] <: C[A] & C[B]
можно вывести:

    A <: A                        B <: B
  ----------                     ---------
  A <: A | B                     B <: A | B
-------------------           ----------------
C[A | B] <: C[A]              C[A | B] <: C[B]
--------------------------------------------------
            C[A | B] <: C[A] & C[B]

Стирание типов

Стертый тип для S & T

— это стираемый glb
(наибольшая нижняя граница) стираемого типа S
и T
. Правила стирания типов пересечения приведены ниже в псевдокоде:

|S & T| = glb(|S|, |T|)

glb(JArray(A), JArray(B)) = JArray(glb(A, B))
glb(JArray(T), _)         = JArray(T)
glb(_, JArray(T))         = JArray(T)
glb(A, B)                 = A                     if A extends B
glb(A, B)                 = B                     if B extends A
glb(A, _)                 = A                     if A is not a trait
glb(_, B)                 = B                     if B is not a trait
glb(A, _)                 = A                     // use first

См. также TypeErasure#erasedGlb

Связь с составным типом (with)

Типы пересечения A & B

заменяют составные типы A with B
. На данный момент синтаксис A with B
по-прежнему разрешен и интерпретируется как A & B
, но его использование в качестве типа (в отличие от предложения new
или extends
) будет объявлено устаревшим и удалено в будущем.


Ссылки:

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.