Scala for Backend Engineering & Functional Programming · Lekcja

Modelowanie za pomocą ADT

Spraw, aby niepoprawnych stanów nie dało się reprezentować

Lekcja 1 z 413 kroki

Modelowanie za pomocą ADT to bezpłatna lekcja Scala for Backend Engineering & Functional Programming na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Scala for Backend Engineering & Functional Programming, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Scala for Backend Engineering & Functional Programming zawiera 4 lekcji w sumie.

Algebraiczne typy danych

Algebraiczne typy danych (ADT) stanowią podstawę funkcyjnego modelowania domeny. Łączą typy iloczynu (AND) i typy sumy (OR), aby precyzyjnie opisywać dane.

  • Iloczyn: rekord z kilkoma polami.
  • Suma: wybór spośród kilku wariantów.
case class Point(x: Int, y: Int) // product type

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

Typy iloczynu

Typ iloczynu grupuje wartości. W Scali case class jest typem iloczynu: instancja przechowuje jednocześnie wszystkie swoje pola.

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)

Typy sumy

Typ sumy to wartość będąca dokładnie jednym z kilku wariantów. Enumy w Scala 3 wyrażają to bezpośrednio.

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)

Spraw, by niepoprawne stany były niemożliwe do przedstawienia

Podstawowa zasada brzmi: projektuj typy tak, aby niepoprawnych danych nie dało się nawet skonstruować. Jeśli system typów uniemożliwia błędny stan, nie trzeba sprawdzać go w czasie działania.

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)

Zły projekt a dobry projekt

Płaski rekord z polami dopuszczającymi wartość null sprzyja powstawaniu niepoprawnych stanów. Zamodelowanie każdego przypadku jako wariantu usuwa te stany.

  • Źle: case class Conn(connected: Boolean, sessionId: String) pozwala na connected=false z ustawionym sessionId.
  • Dobrze: typ sumy wiąże pole z właściwym stanem.
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)

Łączenie iloczynów i sum

Rzeczywiste modele zagnieżdżają typy iloczynu w typach sumy i odwrotnie. Każdy wariant może zawierać własny iloczyn pól.

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)

Dopasowywanie wzorców dla ADT

ADT obsługuje się za pomocą dopasowywania wzorców. Ponieważ typ jest zamknięty, kompilator ostrzega o pominięciu wariantu.

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)))

Rekurencyjne ADT

ADT mogą odwoływać się do samych siebie, dzięki czemu naturalnie modelują drzewa, listy i wyrażenia.

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))

Opcjonalność za pomocą Option

Zamiast null używaj Option do modelowania pola, którego może nie być. Typ jawnie wskazuje brak wartości i wymusza jego obsługę.

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)"))

Precyzyjne modelowanie wielkości

Zastąp obsesję prymitywami. Zamiast surowych wartości Int i String opakowuj znaczące wielkości, aby typy dokumentowały intencję i zapobiegały pomyłkom.

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)

Dlaczego ADT są ważne

ADT zapewniają poprawność i przejrzystość.

  • Niepoprawnych stanów nie da się utworzyć.
  • Kompletne dopasowanie wykrywa pominięte przypadki w czasie kompilacji.
  • Struktura danych dokumentuje domenę.
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)

Szybkie sprawdzenie

Sprawdź swoją wiedzę na temat modelowania opartego na ADT.

Podsumowanie

Poznał(a) Pan/Pani modelowanie domeny za pomocą ADT.

  • Typy iloczynu (case class) łączą pola operacją AND.
  • Typy sumy (enum) oferują wybór za pomocą OR.
  • Zagnieżdżaj je, aby modelować złożone domeny i struktury rekurencyjne.
  • Używaj Option zamiast null.
  • Projektuj typy tak, aby niepoprawnych stanów nie dało się reprezentować.
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")))
Bezpłatny start

Ucz się Scala dzięki korepetycjom AI — za darmo

Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.

Kursy
39
Lekcje
143

Często zadawane pytania

Czy lekcja „Modelowanie za pomocą ADT” jest bezpłatna?

Tak — pełny tekst „Modelowanie za pomocą ADT” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Scala for Backend Engineering & Functional Programming, przejdź na CoddyKit PRO. Kurs Scala for Backend Engineering & Functional Programming zawiera 4 lekcji w sumie.

Co nauczysz się w „Modelowanie za pomocą ADT”?

Spraw, aby niepoprawnych stanów nie dało się reprezentować Ćwiczysz Scala for Backend Engineering & Functional Programming z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Scala for Backend Engineering & Functional Programming?

Nie wymagamy żadnego doświadczenia. Scala for Backend Engineering & Functional Programming w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.

Ile czasu zajmuje lekcja „Modelowanie za pomocą ADT”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Scala for Backend Engineering & Functional Programming?

Tak. Każda lekcja Scala for Backend Engineering & Functional Programming zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Modelowanie za pomocą ADT
  2. Inteligentne konstruktory
  3. Newtype
  4. Komponowanie domen
← Powrót do Scala for Backend Engineering & Functional Programming