0Pricing
Scala for Backend Engineering & Functional Programming · Lektion

Newtypes

Typsichere Wrapper

Newtypes ist eine kostenlose Scala for Backend Engineering & Functional Programming-Lektion auf CoddyKit. Dies ist Lektion 3 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Scala for Backend Engineering & Functional Programming-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Scala for Backend Engineering & Functional Programming-Kurs umfasst insgesamt 4 Lektionen.

Typsichere Wrapper

Ein Newtype ist ein eigenständiger Typ, der einen einzelnen zugrunde liegenden Wert kapselt. Er verhindert die Verwechslung von Werten mit derselben primitiven Darstellung, etwa einer UserId und einer ProductId, die beide Ints sind.

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

Das Problem der Primitive Obsession

Primitive Obsession bezeichnet die übermäßige Verwendung von String und Int für Domänenkonzepte. Dadurch ist es möglich, Unsinn zu übergeben, etwa eine E-Mail-Adresse anstelle eines Namens. Newtypes beheben dies, indem sie jedem Konzept einen eigenen Typ geben.

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 mit Case Classes

Der einfachste Newtype ist eine einfeldrige case class. Sie bietet standardmäßig Gleichheit, eine lesbare toString-Darstellung und Pattern Matching. Der Nachteil ist eine Objektallokation pro Wert.

case class OrderId(value: String)

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

Newtypes mit opaken Typen

Für keine Allokationen implementieren Sie einen Newtype mit einem opaken Typ. Zur Laufzeit verhält er sich wie der zugrunde liegende Typ, ist zur Compile-Zeit aber eigenständig.

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)

Verhalten mit Extensions hinzufügen

Geben Sie einem Newtype über extension-Methoden Operationen und legen Sie dabei nur das offen, was für das Domänenkonzept sinnvoll ist.

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 Funktionssignaturen

Newtypes machen Signaturen selbstdokumentierend und erkennen Fehler durch vertauschte Argumente zur Compile-Zeit. Zwei Parameter mit unterschiedlichen Newtypes können Sie nicht versehentlich vertauschen.

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 und Collections

Eine nach einem Newtype schlüsselnde Map ist klarer und sicherer als eine Map mit rohen Int-Schlüsseln. Der Typ verhindert die Verwendung des falschen Schlüsseltyps.

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

Validierte Newtypes

Kombinieren Sie einen Newtype mit einem Smart Constructor, sodass der Wrapper ebenfalls Invarianten erzwingt. Ein NonEmptyString kann hier niemals leer sein.

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

Eine Darstellung auswählen

Treffen Sie die Auswahl nach Ihren Anforderungen.

  • case class: am einfachsten, für Pattern Matching geeignet, erzeugt ein Objekt.
  • opaque type: ohne Laufzeitkosten und Boxing, ideal für zeitkritische Codepfade und große 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)

Versehentliche Konvertierungen verhindern

Zwei Newtypes über derselben Basis werden nicht implizit konvertiert. Für den Wechsel zwischen ihnen schreiben Sie eine explizite Funktion, die die Absicht dokumentiert.

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

Wann Sie Newtypes verwenden sollten

Verwenden Sie Newtypes immer dann, wenn ein primitiver Typ eine Domänenbedeutung trägt.

  • IDs, Einheiten, formatierte Strings.
  • Verwenden Sie eine Case Class für Einfachheit und einen opaken Typ für Performance.
  • Fügen Sie bei vorhandenen Invarianten einen Smart Constructor hinzu.
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))

Kurzer Check

Testen Sie Ihr Verständnis von Newtypes.

Zusammenfassung

Sie haben Newtypes kennengelernt.

  • Newtypes kapseln einen einzelnen zugrunde liegenden Wert in einem eigenständigen Typ.
  • Sie beheben Primitive Obsession und verhindern vertauschte Argumente.
  • Verwenden Sie case class für Einfachheit und opaque type für keine Laufzeitkosten.
  • Fügen Sie für validierte Wrapper einen Smart Constructor hinzu.
  • Konvertierungen zwischen Newtypes sind immer explizit.
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)

Häufig gestellte Fragen

Ist die Lektion „Newtypes“ kostenlos?

Ja — der vollständige Text von „Newtypes“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Scala for Backend Engineering & Functional Programming-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Scala for Backend Engineering & Functional Programming-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Newtypes“?

Typsichere Wrapper Du übst Scala for Backend Engineering & Functional Programming mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um Scala for Backend Engineering & Functional Programming zu starten?

Keine Vorkenntnisse erforderlich. Scala for Backend Engineering & Functional Programming auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 3 von 4.

Wie lange dauert die Lektion „Newtypes“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser Scala for Backend Engineering & Functional Programming-Lektion Code schreiben und ausführen?

Ja. Jede Scala for Backend Engineering & Functional Programming-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. Modellierung mit ADTs
  2. Intelligente Konstruktoren
  3. Newtypes
  4. Domänen kombinieren
← Zurück zu Scala for Backend Engineering & Functional Programming