0Pricing
Scala for Backend Engineering & Functional Programming · Lezione

Modellazione con gli ADT

Renda gli stati illegali non rappresentabili

Modellazione con gli ADT è una lezione Scala for Backend Engineering & Functional Programming gratuita su CoddyKit. Questa è la lezione 1 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Scala for Backend Engineering & Functional Programming, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Scala for Backend Engineering & Functional Programming include 4 lezioni in totale.

Tipi di dati algebrici

I tipi di dati algebrici (ADT) sono alla base della modellazione funzionale del dominio. Combinano i tipi prodotto (AND) e i tipi somma (OR) per descrivere i dati con precisione.

  • Prodotto: un record con diversi campi.
  • Somma: una scelta tra diverse varianti.
case class Point(x: Int, y: Int) // product type

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

Tipi prodotto

Un tipo prodotto raggruppa più valori. In Scala una case class è un prodotto: un'istanza contiene contemporaneamente tutti i suoi campi.

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)

Tipi somma

Un tipo somma è un valore che corrisponde esattamente a una tra diverse varianti. Gli enum di Scala 3 lo esprimono direttamente.

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)

Rendere irrappresentabili gli stati non validi

Il principio fondamentale è progettare i tipi in modo che i dati non validi non possano nemmeno essere costruiti. Se il sistema dei tipi vieta uno stato errato, non sarà mai necessario verificarlo a runtime.

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)

Cattiva progettazione e buona progettazione

Un record piatto con campi nullable favorisce gli stati non validi. Modellare ogni caso come una variante li elimina.

  • Errato: case class Conn(connected: Boolean, sessionId: String) consente connected=false con un sessionId.
  • Corretto: un tipo somma associa il campo allo stato appropriato.
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)

Combinare prodotti e somme

I modelli reali annidano prodotti nelle somme e viceversa. Ogni variante può contenere il proprio prodotto di campi.

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 sugli ADT

Si usano gli ADT tramite il pattern matching. Poiché il tipo è chiuso, il compilatore avvisa se si omette una variante.

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

ADT ricorsivi

Gli ADT possono fare riferimento a se stessi, modellando naturalmente alberi, liste ed espressioni.

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

Opzionalità con Option

Usi Option invece di null per modellare un campo che potrebbe essere assente. Il tipo rende esplicita l'assenza e impone di gestirla.

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

Modellare le quantità con precisione

Superi la dipendenza eccessiva dai primitivi. Invece di usare Int e String grezzi, racchiuda le quantità significative in tipi che documentano l'intento e impediscono gli scambi.

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)

Perché gli ADT sono importanti

Gli ADT offrono correttezza e chiarezza.

  • Gli stati non validi non possono essere costruiti.
  • Il pattern matching esaustivo rileva i casi mancanti in fase di compilazione.
  • La forma dei dati documenta il dominio.
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)

Verifica rapida

Verifichi la sua comprensione della modellazione basata sugli ADT.

Riepilogo

Ha imparato a modellare il dominio con gli ADT.

  • I tipi prodotto (case class) combinano i campi con AND.
  • I tipi somma (enum) offrono una scelta con OR.
  • Li annidi per modellare domini ricchi e strutture ricorsive.
  • Usi Option invece di null.
  • Progetti i tipi in modo che gli stati non validi non possano essere rappresentati.
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")))

Domande Frequenti

La lezione «Modellazione con gli ADT» è gratuita?

Sì — il testo completo di «Modellazione con gli ADT» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Scala for Backend Engineering & Functional Programming, passa a CoddyKit PRO. Il corso Scala for Backend Engineering & Functional Programming include 4 lezioni in totale.

Cosa imparerò in «Modellazione con gli ADT»?

Renda gli stati illegali non rappresentabili Eserciti Scala for Backend Engineering & Functional Programming con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Scala for Backend Engineering & Functional Programming?

Non è richiesta alcuna esperienza precedente. Scala for Backend Engineering & Functional Programming su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 1 di 4.

Quanto tempo richiede la lezione «Modellazione con gli ADT»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Scala for Backend Engineering & Functional Programming?

Sì. Ogni lezione Scala for Backend Engineering & Functional Programming include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Modellazione con gli ADT
  2. Costruttori intelligenti
  3. Newtype
  4. Composizione dei domini
← Torna a Scala for Backend Engineering & Functional Programming