Newtype
Wrapper con sicurezza dei tipi
Newtype è una lezione Scala for Backend Engineering & Functional Programming gratuita su CoddyKit. Questa è la lezione 3 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.
Wrapper con sicurezza del tipo
Un newtype è un tipo distinto che racchiude un singolo valore sottostante. Impedisce di confondere valori che condividono la stessa rappresentazione primitiva, come un UserId e un ProductId che sono entrambi 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 differIl problema della dipendenza dai primitivi
La dipendenza dai primitivi consiste nell'usare eccessivamente String e Int per rappresentare concetti del dominio. Consente assurdità come passare un'email quando è previsto un nome. I newtype risolvono il problema assegnando a ogni concetto il proprio 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")))Newtype con case class
Il newtype più semplice è una case class con un solo campo. Offre uguaglianza, un metodo toString leggibile e pattern matching già disponibili. Il costo è l'allocazione di un oggetto per ogni valore.
case class OrderId(value: String)
object Main:
def main(args: Array[String]): Unit =
val id = OrderId("ORD-42")
println(id)
println(id.value)Newtype con tipo opaco
Per evitare completamente le allocazioni, implementi un newtype con un tipo opaco. A runtime si comporta come il tipo sottostante, ma in fase di compilazione è 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)Aggiungere comportamento con le extension
Fornisca operazioni a un newtype tramite i metodi extension, esponendo solo ciò che ha senso per il concetto 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)Newtype nelle firme delle funzioni
I newtype rendono le firme autoesplicative e rilevano in fase di compilazione gli errori nell'ordine degli argomenti. Non è possibile scambiare accidentalmente due parametri di newtype diversi.
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)))Newtype e collezioni
Una Map indicizzata con un newtype è più chiara e sicura di una indicizzata con un Int grezzo. Il tipo impedisce di usare la chiave sbagliata.
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)))Newtype validati
Combini un newtype con un costruttore intelligente, in modo che il wrapper imponga anch'esso le invarianti. In questo caso, un NonEmptyString non può mai essere vuoto.
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(""))Scegliere una rappresentazione
Scelga in base alle sue esigenze.
- case class: la più semplice, compatibile con il pattern matching, alloca un oggetto.
- tipo opaco: costo nullo, senza boxing, ideale per percorsi critici e collezioni grandi.
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)Impedire le conversioni accidentali
Due newtype basati sullo stesso tipo non vengono convertiti implicitamente. Per passare dall'uno all'altro è necessario scrivere una funzione esplicita, documentando l'intento.
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)))Quando usare i newtype
Scelga i newtype ogni volta che un tipo primitivo porta con sé un significato di dominio.
- Identificatori, unità di misura, stringhe formattate.
- Usi la case class per semplicità e il tipo opaco per le prestazioni.
- Aggiunga un costruttore intelligente quando sono presenti invarianti.
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))Verifica rapida
Verifichi la sua comprensione dei newtype.
Riepilogo
Ha imparato a usare i newtype.
- I newtype racchiudono un singolo valore sottostante in un tipo distinto.
- Risolvono la dipendenza dai primitivi e impediscono di confondere gli argomenti.
- Usi
case classper semplicità eopaque typeper un costo nullo. - Aggiunga un costruttore intelligente per i wrapper validati.
- Le conversioni tra newtype sono sempre esplicite.
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)Domande Frequenti
La lezione «Newtype» è gratuita?
Sì — il testo completo di «Newtype» è 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 «Newtype»?
Wrapper con sicurezza dei tipi 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 3 di 4.
Quanto tempo richiede la lezione «Newtype»?
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.