0Pricing
Scala for Backend Engineering & Functional Programming · Aula

Modelagem com ADTs

Torne estados ilegais irrepresentáveis

Modelagem com ADTs é uma aula grátis de Scala for Backend Engineering & Functional Programming no CoddyKit. Esta é a aula 1 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de Scala for Backend Engineering & Functional Programming, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Scala for Backend Engineering & Functional Programming inclui 4 aulas no total.

Tipos algébricos de dados

Os tipos algébricos de dados (ADT) são a base da modelagem funcional de domínios. Eles combinam tipos produto (AND) e tipos soma (OR) para descrever dados com precisão.

  • Produto: um registro com vários campos.
  • Soma: uma escolha entre várias variantes.
case class Point(x: Int, y: Int) // product type

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

Tipos produto

Um tipo produto agrupa valores. Na Scala, uma case class é um produto: uma instância contém todos os seus campos ao mesmo tempo.

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 soma

Um tipo soma é um valor que corresponde exatamente a uma entre várias variantes. Os enums da Scala 3 expressam isso diretamente.

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)

Tornando estados ilegais irrepresentáveis

O princípio central é projetar tipos de modo que dados inválidos nem sequer possam ser construídos. Se o sistema de tipos proíbe um estado incorreto, você nunca precisa verificá-lo em tempo de execução.

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)

Projeto ruim versus projeto bom

Um registro plano com campos anuláveis favorece estados ilegais. Modelar cada caso como uma variante elimina esses estados.

  • Ruim: case class Conn(connected: Boolean, sessionId: String) permite connected=false com um sessionId.
  • Bom: um tipo soma associa o campo ao estado correto.
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)

Combinando produtos e somas

Modelos reais aninham produtos dentro de somas e vice-versa. Cada variante pode conter seu próprio produto 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)

Correspondência de padrões com ADT

Você utiliza ADT com correspondência de padrões. Como o tipo é fechado, o compilador avisa se alguma variante não for tratada.

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

Os ADT podem referenciar a si mesmos, modelando árvores, listas e expressões naturalmente.

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

Opcionalidade com Option

Use Option em vez de null para modelar um campo que pode estar ausente. O tipo torna a ausência explícita e obriga seu tratamento.

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

Modelando quantidades com precisão

Substitua a obsessão por tipos primitivos. Em vez de Ints e Strings brutos, envolva quantidades com significado para que os tipos documentem a intenção e evitem confusões.

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 que os ADT são importantes

Os ADT proporcionam correção e clareza.

  • Não é possível construir estados inválidos.
  • A correspondência exaustiva detecta casos não tratados em tempo de compilação.
  • A forma dos dados documenta o domínio.
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ção rápida

Teste sua compreensão da modelagem baseada em ADT.

Recapitulação

Você aprendeu a modelar domínios com ADT.

  • Os tipos produto (case class) combinam campos com AND.
  • Os tipos soma (enum) oferecem uma escolha com OR.
  • Aninhe-os para modelar domínios ricos e estruturas recursivas.
  • Use Option em vez de null.
  • Projete os tipos para que estados ilegais não possam ser representados.
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")))

Perguntas Frequentes

A aula “Modelagem com ADTs” é grátis?

Sim — o texto completo de “Modelagem com ADTs” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de Scala for Backend Engineering & Functional Programming, atualize para CoddyKit PRO. O curso de Scala for Backend Engineering & Functional Programming inclui 4 aulas no total.

O que vou aprender em “Modelagem com ADTs”?

Torne estados ilegais irrepresentáveis Você pratica Scala for Backend Engineering & Functional Programming com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar Scala for Backend Engineering & Functional Programming?

Nenhuma experiência prévia é necessária. Scala for Backend Engineering & Functional Programming no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 1 de 4.

Quanto tempo leva a aula “Modelagem com ADTs”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de Scala for Backend Engineering & Functional Programming?

Sim. Cada aula de Scala for Backend Engineering & Functional Programming inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. Modelagem com ADTs
  2. Construtores inteligentes
  3. Newtypes
  4. Composição de domínios
← Voltar para Scala for Backend Engineering & Functional Programming