Моделирование с помощью 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
Optioninstead 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 — локальная установка не требуется.
Все уроки этого курса
- Моделирование с помощью ADT
- Умные конструкторы
- Newtype-типы
- Составные предметные области