0Pricing
Scala for Backend Engineering & Functional Programming · Aula

Newtypes

Envoltórios seguros quanto aos tipos

Newtypes é uma aula grátis de Scala for Backend Engineering & Functional Programming no CoddyKit. Esta é a aula 3 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.

Partes desta aula ainda não foram traduzidas e aparecem em inglês.

Type-Safe Wrappers

A newtype is a distinct type that wraps a single underlying value. It prevents mixing up values that share a primitive representation, like a UserId and a ProductId that are both Ints.

case class UserId(value: Int)
case class ProductId(value: Int)

object Main:
  def main(args: Array[String]): Unit =
    val u = UserId(1)
    val p = ProductId(1)
    println(u.value == p.value) // values equal, types differ

The Primitive Obsession Problem

Primitive obsession is overusing String and Int for domain concepts. It allows nonsense like passing an email where a name belongs. Newtypes fix this by giving each concept its own type.

case class Email(value: String)
case class City(value: String)

object Main:
  def greet(c: City): String = s"Welcome to ${c.value}"

  def main(args: Array[String]): Unit =
    println(greet(City("Berlin")))

Case Class Newtypes

The simplest newtype is a single-field case class. It gives equality, a readable toString, and pattern matching out of the box. The cost is one object allocation per value.

case class OrderId(value: String)

object Main:
  def main(args: Array[String]): Unit =
    val id = OrderId("ORD-42")
    println(id)
    println(id.value)

Opaque Type Newtypes

For zero allocation, implement a newtype with an opaque type. It behaves like the underlying type at runtime but is distinct at compile time.

object Ids:
  opaque type UserId = Int
  def apply(i: Int): UserId = i
  extension (u: UserId) def value: Int = u

object Main:
  def main(args: Array[String]): Unit =
    val u = Ids(7)
    println(u.value)

Adding Behavior with Extensions

Give a newtype operations through extension methods, exposing only what makes sense for the domain concept.

object Money:
  opaque type Cents = Long
  def apply(n: Long): Cents = n
  extension (c: Cents)
    def +(o: Cents): Cents = c + o
    def toDollars: Double = c / 100.0

object Main:
  def main(args: Array[String]): Unit =
    val total = Money(250) + Money(750)
    println(total.toDollars)

Newtypes in Function Signatures

Newtypes make signatures self-documenting and catch argument-order bugs at compile time. You cannot accidentally swap two parameters of different newtypes.

case class Width(value: Int)
case class Height(value: Int)

object Main:
  def area(w: Width, h: Height): Int = w.value * h.value

  def main(args: Array[String]): Unit =
    println(area(Width(4), Height(5)))

Newtypes and Collections

A Map keyed by a newtype is clearer and safer than one keyed by a raw Int. The type prevents using the wrong kind of key.

case class UserId(value: Int)

object Main:
  def main(args: Array[String]): Unit =
    val names = Map(UserId(1) -> "Ada", UserId(2) -> "Bob")
    println(names(UserId(2)))

Validated Newtypes

Combine a newtype with a smart constructor so the wrapper also enforces invariants. Here a NonEmptyString can never be empty.

case class NonEmptyString private (value: String)
object NonEmptyString:
  def of(s: String): Option[NonEmptyString] =
    if s.nonEmpty then Some(NonEmptyString(s)) else None

object Main:
  def main(args: Array[String]): Unit =
    println(NonEmptyString.of("hi"))
    println(NonEmptyString.of(""))

Choosing a Representation

Pick based on your needs.

  • case class: easiest, pattern-matchable, allocates an object.
  • opaque type: zero-cost, no boxing, ideal for hot paths and big collections.
object Temp:
  opaque type Kelvin = Double
  def apply(d: Double): Kelvin = d
  extension (k: Kelvin) def value: Double = k

object Main:
  def main(args: Array[String]): Unit =
    val readings = List(Temp(300.0), Temp(310.5))
    println(readings.map(_.value).sum)

Preventing Accidental Conversions

Two newtypes over the same base do not implicitly convert. To go between them you write an explicit function, documenting the intent.

case class Meters(value: Double)
case class Feet(value: Double)

object Main:
  def toFeet(m: Meters): Feet = Feet(m.value * 3.281)

  def main(args: Array[String]): Unit =
    println(toFeet(Meters(2.0)))

When to Use Newtypes

Reach for newtypes whenever a primitive carries domain meaning.

  • Identifiers, units, formatted strings.
  • Use case class for simplicity, opaque type for performance.
  • Add a smart constructor when there are invariants.
object Domain:
  opaque type Sku = String
  def of(s: String): Option[Sku] =
    if s.startsWith("SKU-") then Some(s) else None
  extension (k: Sku) def raw: String = k

object Main:
  def main(args: Array[String]): Unit =
    println(Domain.of("SKU-9").map(_.raw))

Quick Check

Test your understanding of newtypes.

Recap

You learned newtypes.

  • Newtypes wrap one underlying value in a distinct type.
  • They cure primitive obsession and prevent argument mixups.
  • Use case class for simplicity, opaque type for zero cost.
  • Add a smart constructor for validated wrappers.
  • Conversions between newtypes are always explicit.
object Ids:
  opaque type AccountId = Long
  def apply(n: Long): AccountId = n
  extension (a: AccountId) def value: Long = a

object Main:
  def main(args: Array[String]): Unit =
    val acc = Ids(1001L)
    println(acc.value)

Perguntas Frequentes

A aula “Newtypes” é grátis?

Sim — o texto completo de “Newtypes” é 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 “Newtypes”?

Envoltórios seguros quanto aos tipos 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 3 de 4.

Quanto tempo leva a aula “Newtypes”?

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