0Pricing
Scala for Backend Engineering & Functional Programming · Lezione

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 differ

Il 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.
  • Risol­vono la dipendenza dai primitivi e impediscono di confondere gli argomenti.
  • Usi case class per semplicità e opaque type per 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.

Tutte le lezioni di questo corso

  1. Modellazione con gli ADT
  2. Costruttori intelligenti
  3. Newtype
  4. Composizione dei domini
← Torna a Scala for Backend Engineering & Functional Programming