0Pricing
Scala for Backend Engineering & Functional Programming · Урок

Алгебраические типы данных

Моделируйте предметную область

«Алгебраические типы данных» — бесплатный урок Scala for Backend Engineering & Functional Programming на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Scala for Backend Engineering & Functional Programming, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Scala for Backend Engineering & Functional Programming содержит 4 уроков всего.

Что такое ADT

Алгебраический тип данных (ADT) — это тип, созданный объединением других типов. В Scala ADT моделируют с помощью sealed trait и вариантов case class и case object.

ADT позволяют точно описывать структуру данных предметной области.

Типы-суммы

Тип-сумма означает, что значение является одним из нескольких вариантов (OR). Светофор — это красный OR жёлтый OR зелёный.

Это выражается с помощью запечатанного трейта и нескольких вариантов.

sealed trait Light
case object Red extends Light
case object Yellow extends Light
case object Green extends Light
object Main {
  def main(args: Array[String]): Unit = {
    val l: Light = Yellow
    println(l)
  }
}

Типы-произведения

Тип-произведение означает, что значение объединяет несколько полей (AND). Точка имеет координату x AND координату y.

Классы вариантов являются типами-произведениями.

case class Point(x: Int, y: Int)
object Main {
  def main(args: Array[String]): Unit = {
    val p = Point(3, 4)
    println(s"${p.x} and ${p.y}")
  }
}

Сочетание типов-сумм и типов-произведений

Настоящие ADT объединяют оба подхода: запечатанный трейт (сумму), варианты которого являются классами вариантов (произведениями).

Здесь Shape — это Circle (с радиусом) OR Rectangle (с шириной и высотой).

sealed trait Shape
case class Circle(radius: Double) extends Shape
case class Rectangle(w: Double, h: Double) extends Shape
object Main {
  def main(args: Array[String]): Unit = {
    val s: Shape = Rectangle(3, 4)
    println(s)
  }
}

Работа с ADT

ADT обрабатывают с помощью сопоставления с образцом по его вариантам. Каждая ветвь обрабатывает одну фигуру.

sealed trait Shape
case class Circle(radius: Double) extends Shape
case class Rectangle(w: Double, h: Double) extends Shape
object Main {
  def area(s: Shape): Double = s match {
    case Circle(r)       => 3.14159 * r * r
    case Rectangle(w, h) => w * h
  }
  def main(args: Array[String]): Unit = {
    println(area(Circle(2)))
    println(area(Rectangle(3, 4)))
  }
}

Моделирование предметной области

ADT делают недопустимые состояния непредставимыми. Смоделируйте способ оплаты как закрытый набор допустимых вариантов.

sealed trait PaymentMethod
case class CreditCard(number: String) extends PaymentMethod
case class BankTransfer(iban: String) extends PaymentMethod
case object Cash extends PaymentMethod
object Main {
  def main(args: Array[String]): Unit = {
    val m: PaymentMethod = Cash
    println(m)
  }
}

Рекурсивные ADT

ADT может ссылаться на самого себя — так моделируют деревья и списки. Вот простое двоичное дерево.

sealed trait Tree
case object Leaf extends Tree
case class Node(value: Int, left: Tree, right: Tree) extends Tree
object Main {
  def main(args: Array[String]): Unit = {
    val t: Tree = Node(1, Leaf, Node(2, Leaf, Leaf))
    println(t)
  }
}

Свёртка рекурсивного ADT

Рекурсия вместе с сопоставлением позволяет обработать всю структуру. Здесь мы суммируем каждое значение в дереве.

sealed trait Tree
case object Leaf extends Tree
case class Node(value: Int, left: Tree, right: Tree) extends Tree
object Main {
  def sum(t: Tree): Int = t match {
    case Leaf            => 0
    case Node(v, l, r)   => v + sum(l) + sum(r)
  }
  def main(args: Array[String]): Unit = {
    val t = Node(1, Node(2, Leaf, Leaf), Node(3, Leaf, Leaf))
    println(sum(t))
  }
}

Зачем нужны ADT

ADT мощны, потому что они:

  • Точно описывают данные как суммы и произведения
  • Делают невозможным создание недопустимых состояний
  • Идеально сочетаются с полным сопоставлением с образцом
  • Масштабируются до рекурсивных структур, таких как деревья и списки

ADT и наследование

Традиционный ООП-код распределял бы поведение по подклассам. ADT оставляют определения данных закрытыми, а поведение помещают в функции, выполняющие сопоставление с образцом.

Такое разделение данных и поведения — отличительная черта функционального проектирования.

Объединяем всё вместе

Полный небольшой ADT с функцией, которая обрабатывает каждый вариант.

sealed trait Json
case class JNum(n: Double) extends Json
case class JStr(s: String) extends Json
case object JNull extends Json
object Main {
  def render(j: Json): String = j match {
    case JNum(n) => n.toString
    case JStr(s) => "\"" + s + "\""
    case JNull   => "null"
  }
  def main(args: Array[String]): Unit = {
    List(JNum(3.0), JStr("hi"), JNull).foreach(j => println(render(j)))
  }
}

Быстрая проверка

Проверьте, насколько хорошо вы поняли ADT.

Итоги

Вы научились создавать алгебраические типы данных:

  • Типы-суммы: запечатанный трейт с несколькими вариантами (OR)
  • Типы-произведения: классы вариантов с полями (AND)
  • Объединяйте их для точного моделирования предметных областей
  • ADT могут быть рекурсивными (деревья, списки)
  • Обрабатывайте их с помощью сопоставления с образцом

Часто задаваемые вопросы

Урок «Алгебраические типы данных» бесплатный?

Да — полный текст урока «Алгебраические типы данных» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 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 структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Алгебраические типы данных»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Scala for Backend Engineering & Functional Programming?

Да. Каждый урок Scala for Backend Engineering & Functional Programming включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Классы case
  2. Запечатанные трейты
  3. Алгебраические типы данных
  4. Полное сопоставление
← Назад к Scala for Backend Engineering & Functional Programming