0Pricing
Scala for Backend Engineering & Functional Programming · Lección

Modelado con ADT

Hacer que los estados ilegales sean irrepresentables

Modelado con ADT es una lección gratuita de Scala for Backend Engineering & Functional Programming en CoddyKit. Esta es la lección 1 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Scala for Backend Engineering & Functional Programming, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Scala for Backend Engineering & Functional Programming incluye 4 lecciones en total.

Tipos de datos algebraicos

Los tipos de datos algebraicos (ADT) son la base del modelado funcional de dominios. Combinan tipos producto (AND) y tipos suma (OR) para describir los datos con precisión.

  • Producto: un registro con varios campos.
  • Suma: una elección entre varias variantes.
case class Point(x: Int, y: Int) // product type

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

Tipos producto

Un tipo producto agrupa varios valores. En Scala, una case class es un producto: una instancia contiene todos sus campos a la vez.

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)

Tipos suma

Un tipo suma es un valor que es exactamente una de varias variantes. Los enums de Scala 3 lo expresan directamente.

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)

Haga que los estados ilegales sean irrepresentables

El principio fundamental es diseñar los tipos de modo que los datos no válidos ni siquiera puedan construirse. Si el sistema de tipos prohíbe un estado incorrecto, nunca necesitará comprobarlo en tiempo de ejecución.

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)

Mal diseño frente a buen diseño

Un registro plano con campos anulables propicia estados ilegales. Modelar cada caso como una variante los elimina.

  • Mal: case class Conn(connected: Boolean, sessionId: String) permite connected=false con un sessionId.
  • Bien: un tipo suma vincula el campo con el estado correcto.
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)

Combinar productos y sumas

Los modelos reales anidan productos dentro de sumas y viceversa. Cada variante puede contener su propio producto de campos.

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

Los ADT se utilizan mediante pattern matching. Como el tipo está cerrado, el compilador avisa si omite alguna 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 recursivos

Los ADT pueden referirse a sí mismos, lo que permite modelar árboles, listas y expresiones de forma natural.

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

Opcionalidad con Option

Use Option en lugar de null para modelar un campo que puede estar ausente. El tipo hace explícita la ausencia y obliga a gestionarla.

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

Modelar cantidades con precisión

Evite la obsesión por los tipos primitivos. En lugar de usar Int y String sin más, envuelva las cantidades con significado para que los tipos documenten la intención y eviten confusiones.

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)

Por qué son importantes los ADT

Los ADT aportan corrección y claridad.

  • No se pueden construir estados inválidos.
  • El pattern matching exhaustivo detecta los casos omitidos en tiempo de compilación.
  • La forma de los datos documenta el 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)

Comprobación rápida

Compruebe su comprensión del modelado basado en ADT.

Resumen

Ha aprendido a modelar dominios con ADT.

  • Los tipos producto (case class) combinan campos con AND.
  • Los tipos suma (enum) ofrecen una elección con OR.
  • Ánelos para modelar dominios complejos y estructuras recursivas.
  • Use Option en lugar de null.
  • Diseñe los tipos de modo que los estados ilegales no puedan representarse.
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")))

Preguntas frecuentes

¿La lección «Modelado con ADT» es gratis?

Sí — el texto completo de «Modelado con ADT» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Scala for Backend Engineering & Functional Programming, actualiza a CoddyKit PRO. El curso de Scala for Backend Engineering & Functional Programming incluye 4 lecciones en total.

¿Qué aprenderé en «Modelado con ADT»?

Hacer que los estados ilegales sean irrepresentables Practicas Scala for Backend Engineering & Functional Programming con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar Scala for Backend Engineering & Functional Programming?

No se requiere experiencia previa. Scala for Backend Engineering & Functional Programming en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 1 de 4.

¿Cuánto tiempo toma la lección «Modelado con ADT»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de Scala for Backend Engineering & Functional Programming?

Sí. Cada lección de Scala for Backend Engineering & Functional Programming incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Modelado con ADT
  2. Constructores inteligentes
  3. Newtypes
  4. Composición de dominios
← Volver a Scala for Backend Engineering & Functional Programming