Типы объединения и пересечения
Новые возможности типов
«Типы объединения и пересечения» — бесплатный урок Scala for Backend Engineering & Functional Programming на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Scala for Backend Engineering & Functional Programming, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Scala for Backend Engineering & Functional Programming содержит 4 уроков всего.
Новые возможности типов
Scala 3 добавляет в систему типов объединяющие типы (A | B) и пересекающиеся типы (A & B). Они позволяют точнее описывать значения без иерархий наследования.
object Main:
def main(args: Array[String]): Unit =
println("Union: A | B, Intersection: A & B")Основы объединяющих типов
Объединяющий тип A | B представляет значение, являющееся либо A, либо B. С точки зрения теории множеств это операция OR над типами.
object Main:
def show(x: Int | String): String = x match
case i: Int => s"int $i"
case s: String => s"str $s"
def main(args: Array[String]): Unit =
println(show(42))
println(show("hi"))Объединениям не нужен общий супертип
В отличие от Either в Scala 2, объединяющие типы не требуют обёртки. Любые два типа можно объединить, даже если у них нет общего родительского класса.
object Main:
def parse(input: Int | Boolean): String =
input match
case n: Int => s"number $n"
case b: Boolean => s"flag $b"
def main(args: Array[String]): Unit =
println(parse(7))
println(parse(true))Объединения для моделирования ошибок
Объединяющие типы удобны для лёгкого представления результатов с ошибками без типа-обёртки. В сочетании с перечислениями или одиночными объектами они выглядят лаконично.
case object NotFound
object Main:
def lookup(id: Int): String | NotFound.type =
if id == 1 then "Alice" else NotFound
def main(args: Array[String]): Unit =
println(lookup(1))
println(lookup(2))Основы пересекающихся типов
Пересекающийся тип A & B представляет значение, которое является одновременно и A, и B. С точки зрения теории множеств это операция AND, часто используемая для объединения трейтов.
trait Named:
def name: String
trait Aged:
def age: Int
object Main:
def describe(p: Named & Aged): String =
s"${p.name} is ${p.age}"
def main(args: Array[String]): Unit =
val person = new Named with Aged:
def name = "Bob"
def age = 40
println(describe(person))Пересечение коммутативно
Порядок не имеет значения для пересекающихся типов: A & B — это тот же тип, что и B & A. Это отличается от составных типов Scala 2 A with B, для которых порядок в некоторых разрешениях членов имел значение.
trait Readable:
def read: String
trait Writable:
def write(s: String): Unit
object Main:
def use(r: Readable & Writable): Unit =
r.write("x")
println(r.read)
def main(args: Array[String]): Unit =
val rw = new Readable with Writable:
def read = "data"
def write(s: String) = println(s"wrote $s")
use(rw)Объединение нескольких трейтов
Можно пересекать более двух типов. Результат требует наличия всех перечисленных возможностей.
trait A: def a: Int
trait B: def b: Int
trait C: def c: Int
object Main:
def sum(x: A & B & C): Int = x.a + x.b + x.c
def main(args: Array[String]): Unit =
val v = new A with B with C:
def a = 1
def b = 2
def c = 3
println(sum(v))Псевдонимы типов для объединений
Длинным объединяющим типам можно присвоить имя с помощью псевдонима type, чтобы сигнатуры оставались читаемыми.
type Json = Int | String | Boolean
object Main:
def render(j: Json): String = j match
case i: Int => i.toString
case s: String => s"\"$s\""
case b: Boolean => b.toString
def main(args: Array[String]): Unit =
println(render("hi"))
println(render(true))Полное сопоставление объединений
Компилятор знает элементы объединения, поэтому может проверить полноту сопоставления. Обработка каждого варианта делает сопоставление исчерпывающим.
type Shape = "circle" | "square"
object Main:
def sides(s: Shape): Int = s match
case "circle" => 0
case "square" => 4
def main(args: Array[String]): Unit =
println(sides("square"))Расширение объединений
При присваивании значения объединяющего типа переменной с выведенным типом Scala сохраняет объединяющий тип. Однако некоторые операции расширяют его до наименьшей общей верхней границы, поэтому указывайте тип явно, если хотите сохранить объединение.
object Main:
def pick(flag: Boolean): Int | String =
if flag then 1 else "one"
def main(args: Array[String]): Unit =
val x: Int | String = pick(false)
println(x)Когда что использовать
Выбирайте подходящий инструмент.
- Объединение: значение имеет один из нескольких типов (OR).
- Пересечение: значение одновременно удовлетворяет нескольким трейтам (AND).
- Объединения позволяют избежать типов-обёрток, а пересечения — глубокой иерархии наследования.
trait Logger: def log(s: String): Unit
trait Clock: def now: Long
object Main:
def run(env: Logger & Clock): Unit =
env.log(s"time=${env.now}")
def main(args: Array[String]): Unit =
val env = new Logger with Clock:
def log(s: String) = println(s)
def now = 100L
run(env)Быстрая проверка
Проверьте, насколько хорошо Вы поняли объединяющие и пересекающиеся типы.
Итоги
Вы изучили объединяющие и пересекающиеся типы Scala 3.
A | B: значение является A или B; обёртка и общий родитель не нужны.A & B: значение одновременно является A и B; операция коммутативна.- Используйте сопоставление с образцом, чтобы сузить объединения, и объединяйте трейты с помощью пересечений.
- Используйте псевдонимы
type, чтобы давать имена длинным объединениям.
object Main:
def length(x: String | List[Int]): Int = x match
case s: String => s.length
case l: List[Int] => l.length
def main(args: Array[String]): Unit =
println(length("hello"))
println(length(List(1, 2, 3)))Часто задаваемые вопросы
Урок «Типы объединения и пересечения» бесплатный?
Да — полный текст урока «Типы объединения и пересечения» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Scala for Backend Engineering & Functional Programming, подпишись на CoddyKit PRO. Курс Scala for Backend Engineering & Functional Programming содержит 4 уроков всего.
Чему я научусь в уроке «Типы объединения и пересечения»?
Новые возможности типов Ты практикуешь Scala for Backend Engineering & Functional Programming с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Scala for Backend Engineering & Functional Programming?
Предыдущий опыт не требуется. Scala for Backend Engineering & Functional Programming на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Типы объединения и пересечения»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Scala for Backend Engineering & Functional Programming?
Да. Каждый урок Scala for Backend Engineering & Functional Programming включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Значимые отступы
- Перечисления в Scala 3
- Непрозрачные типы
- Типы объединения и пересечения