Newtypes
Envoltorios seguros por tipos
Newtypes es una lección gratuita de Scala for Backend Engineering & Functional Programming en CoddyKit. Esta es la lección 3 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.
Envoltorios seguros respecto a los tipos
Un newtype es un tipo distinto que envuelve un único valor subyacente. Evita confundir valores que comparten una representación primitiva, como un UserId y un ProductId que son ambos Int.
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 differEl problema de la obsesión por los tipos primitivos
La obsesión por los tipos primitivos consiste en usar String e Int en exceso para conceptos del dominio. Permite absurdos como pasar un correo electrónico donde corresponde un nombre. Los newtypes corrigen esto al asignar a cada concepto su propio tipo.
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")))Newtypes con case class
El newtype más sencillo es una case class de un solo campo. Proporciona igualdad, un toString legible y pattern matching desde el principio. El coste es asignar un objeto por cada valor.
case class OrderId(value: String)
object Main:
def main(args: Array[String]): Unit =
val id = OrderId("ORD-42")
println(id)
println(id.value)Newtypes con tipos opacos
Para no realizar asignaciones, implemente un newtype con un tipo opaco. En tiempo de ejecución se comporta como el tipo subyacente, pero en tiempo de compilación es distinto.
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)Añadir comportamiento con extensiones
Proporcione operaciones a un newtype mediante métodos extension y exponga solo las que tengan sentido para el concepto del dominio.
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 en las firmas de funciones
Los newtypes hacen que las firmas se documenten por sí mismas y detectan en tiempo de compilación los errores en el orden de los argumentos. No puede intercambiar accidentalmente dos parámetros de distintos 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 y colecciones
Un Map indexado por un newtype es más claro y seguro que uno indexado por un Int sin más. El tipo impide usar la clase de clave incorrecta.
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)))Newtypes validados
Combine un newtype con un constructor inteligente para que el envoltorio también aplique las invariantes. Aquí, un NonEmptyString nunca puede estar vacío.
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(""))Elegir una representación
Elija según sus necesidades.
- case class: la opción más sencilla, compatible con pattern matching y con asignación de un objeto.
- opaque type: coste cero, sin boxing, ideal para rutas críticas y colecciones grandes.
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)Evitar conversiones accidentales
Dos newtypes basados en el mismo tipo no se convierten implícitamente. Para pasar de uno a otro debe escribir una función explícita que documente la intención.
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)))Cuándo usar newtypes
Use newtypes siempre que un tipo primitivo tenga significado dentro del dominio.
- Identificadores, unidades y cadenas con formato.
- Use case class para simplificar y opaque type para obtener rendimiento.
- Añada un constructor inteligente cuando existan invariantes.
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))Comprobación rápida
Compruebe su comprensión de los newtypes.
Resumen
Ha aprendido los newtypes.
- Los newtypes envuelven un valor subyacente en un tipo distinto.
- Corrigen la obsesión por los tipos primitivos y evitan confundir argumentos.
- Use
case classpara simplificar yopaque typepara obtener coste cero. - Añada un constructor inteligente para crear envoltorios validados.
- Las conversiones entre newtypes siempre son explícitas.
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)Preguntas frecuentes
¿La lección «Newtypes» es gratis?
Sí — el texto completo de «Newtypes» 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 «Newtypes»?
Envoltorios seguros por tipos 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 3 de 4.
¿Cuánto tiempo toma la lección «Newtypes»?
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.