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

Моделирование с помощью ADT

Сделайте недопустимые состояния непредставимыми

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

Части этого урока еще не переведены и отображаются на английском.

Algebraic Data Types

Algebraic data types (ADTs) are the foundation of functional domain modeling. They combine product types (AND) and sum types (OR) to describe data precisely.

  • Product: a record with several fields.
  • Sum: a choice among several variants.
case class Point(x: Int, y: Int) // product type

object Main:
  def main(args: Array[String]): Unit =
    println(Point(1, 2))

Product Types

A product type bundles values together. In Scala a case class is a product: an instance holds all its fields at once.

case class User(name: String, age: Int)

object Main:
  def main(args: Array[String]): Unit =
    val u = User("Ada", 36)
    println(u.name)
    println(u.age)

Sum Types

A sum type is a value that is exactly one of several variants. Scala 3 enums express this directly.

enum PaymentMethod:
  case Cash
  case Card(number: String)
  case Crypto(wallet: String)

object Main:
  def main(args: Array[String]): Unit =
    val p: PaymentMethod = PaymentMethod.Card("1234")
    println(p)

Make Illegal States Unrepresentable

The core principle: design types so that invalid data cannot even be constructed. If the type system forbids a bad state, you never need a runtime check for it.

enum Connection:
  case Disconnected
  case Connected(sessionId: String)

object Main:
  def main(args: Array[String]): Unit =
    // No way to have a sessionId while Disconnected
    val c: Connection = Connection.Connected("abc")
    println(c)

Bad Design vs Good Design

A flat record with nullable fields invites illegal states. Modeling each case as a variant removes them.

  • Bad: case class Conn(connected: Boolean, sessionId: String) allows connected=false with a sessionId.
  • Good: a sum type ties the field to the right state.
enum Door:
  case Open
  case Closed
  case Locked(key: String)

object Main:
  def main(args: Array[String]): Unit =
    val d: Door = Door.Locked("k1")
    println(d)

Combining Products and Sums

Real models nest products inside sums and vice versa. Each variant can carry its own product of fields.

case class Address(city: String, zip: String)

enum Contact:
  case Email(value: String)
  case Postal(address: Address)

object Main:
  def main(args: Array[String]): Unit =
    val c: Contact = Contact.Postal(Address("Paris", "75001"))
    println(c)

Pattern Matching ADTs

You consume ADTs with pattern matching. Because the type is closed, the compiler warns if you miss a variant.

enum Shape:
  case Circle(r: Double)
  case Rect(w: Double, h: Double)

object Main:
  def area(s: Shape): Double = s match
    case Shape.Circle(r)  => 3.14159 * r * r
    case Shape.Rect(w, h) => w * h

  def main(args: Array[String]): Unit =
    println(area(Shape.Rect(2, 3)))

Recursive ADTs

ADTs can refer to themselves, modeling trees, lists, and expressions naturally.

enum Expr:
  case Num(value: Int)
  case Add(left: Expr, right: Expr)

object Main:
  def eval(e: Expr): Int = e match
    case Expr.Num(v)    => v
    case Expr.Add(l, r) => eval(l) + eval(r)

  def main(args: Array[String]): Unit =
    val e = Expr.Add(Expr.Num(2), Expr.Num(3))
    println(eval(e))

Optionality with Option

Use Option instead of null to model a possibly-absent field. The type makes the absence explicit and forces handling.

case class Profile(name: String, nickname: Option[String])

object Main:
  def main(args: Array[String]): Unit =
    val p = Profile("Grace", None)
    println(p.nickname.getOrElse("(none)"))

Modeling Quantities Precisely

Replace primitive obsession. Instead of raw Ints and Strings, wrap meaningful quantities so the types document intent and prevent mixups.

case class Quantity(value: Int)
case class Price(cents: Long)
case class LineItem(qty: Quantity, price: Price)

object Main:
  def main(args: Array[String]): Unit =
    val item = LineItem(Quantity(3), Price(500))
    println(item)

Why ADTs Matter

ADTs give you correctness and clarity.

  • Invalid states cannot be built.
  • Exhaustive matching catches missed cases at compile time.
  • The shape of the data documents the domain.
enum OrderStatus:
  case Pending
  case Shipped(tracking: String)
  case Delivered(at: Long)

object Main:
  def main(args: Array[String]): Unit =
    val s: OrderStatus = OrderStatus.Shipped("TRK1")
    println(s)

Quick Check

Test your understanding of ADT-based modeling.

Recap

You learned domain modeling with ADTs.

  • Product types (case class) combine fields with AND.
  • Sum types (enum) offer a choice with OR.
  • Nest them to model rich domains and recursive structures.
  • Use Option instead of null.
  • Design so illegal states cannot be represented.
enum Event:
  case Created(id: Int)
  case Deleted(id: Int, reason: String)

object Main:
  def describe(e: Event): String = e match
    case Event.Created(id)         => s"created $id"
    case Event.Deleted(id, reason) => s"deleted $id: $reason"

  def main(args: Array[String]): Unit =
    println(describe(Event.Deleted(1, "spam")))

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

Урок «Моделирование с помощью ADT» бесплатный?

Да — полный текст урока «Моделирование с помощью ADT» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Scala for Backend Engineering & Functional Programming, подпишись на CoddyKit PRO. Курс Scala for Backend Engineering & Functional Programming содержит 4 уроков всего.

Чему я научусь в уроке «Моделирование с помощью ADT»?

Сделайте недопустимые состояния непредставимыми Ты практикуешь Scala for Backend Engineering & Functional Programming с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Scala for Backend Engineering & Functional Programming?

Предыдущий опыт не требуется. Scala for Backend Engineering & Functional Programming на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.

Сколько времени занимает урок «Моделирование с помощью ADT»?

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

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

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

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

  1. Моделирование с помощью ADT
  2. Умные конструкторы
  3. Newtype-типы
  4. Составные предметные области
← Назад к Scala for Backend Engineering & Functional Programming