Scala til backendudvikling og funktionel programmering · Lektion

Newtypes

Typesikre wrappers

Lektion 3 af 413 trin

Newtypes er en gratis Scala til backendudvikling og funktionel programmering-lektion på CoddyKit. Dette er lektion 3 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i Scala til backendudvikling og funktionel programmering, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Scala til backendudvikling og funktionel programmering-kurset indeholder 4 lektioner i alt.

Typesikre indpakninger

En newtype er en særskilt type, der indpakker én underliggende værdi. Den forhindrer, at værdier med samme primitive repræsentation forveksles, f.eks. en UserId og en ProductId, der begge er 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

Problemet med primitive typer

Primitive obsession betyder, at man bruger String og Int for meget til domænebegreber. Det tillader meningsløse ting som at sende en e-mailadresse, hvor der forventes et navn. Newtypes løser dette ved at give hvert begreb sin egen 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")))

Newtypes med case class

Den enkleste newtype er en case class med ét felt. Den giver lighed, en læsbar toString og pattern matching direkte fra start. Ulempen er, at der allokeres ét objekt pr. værdi.

case class OrderId(value: String)

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

Newtypes med opaque type

Hvis du vil undgå allokering, kan du implementere en newtype med en opaque type. Den opfører sig som den underliggende type ved kørsel, men er særskilt ved kompilering.

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)

Tilføj funktionalitet med extensions

Giv en newtype operationer via extension-metoder, og eksponér kun det, der giver mening for domænebegrebet.

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 i funktionssignaturer

Newtypes gør signaturer selvforklarende og opdager fejl i argumentrækkefølgen ved kompilering. Du kan ikke ved et uheld bytte om på to parametre med forskellige 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 og samlinger

En Map med en newtype som nøgle er tydeligere og sikrere end en med en rå Int som nøgle. Typen forhindrer brug af den forkerte slags nøgle.

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

Validerede newtypes

Kombinér en newtype med en smart konstruktør, så indpakningen også håndhæver invariansregler. Her kan en NonEmptyString aldrig være tom.

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

Valg af repræsentation

Vælg ud fra dine behov.

  • case class: nemmest, kan bruges med pattern matching og allokerer et objekt.
  • opaque type: ingen omkostning, ingen boxing, ideel til tidskritiske dele af programmet og store samlinger.
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)

Forebyggelse af utilsigtede konverteringer

To newtypes baseret på den samme grundtype konverteres ikke implicit. Hvis du vil konvertere mellem dem, skal du skrive en eksplicit funktion, som dokumenterer hensigten.

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

Hvornår skal du bruge newtypes

Brug newtypes, når en primitiv type har en betydning i domænet.

  • Identifikatorer, enheder og formaterede strenge.
  • Brug case class for enkelhed og opaque type for ydeevne.
  • Tilføj en smart konstruktør, når der findes invariansregler.
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))

Hurtigt tjek

Test din forståelse af newtypes.

Opsummering

Du har lært om newtypes.

  • Newtypes indpakker én underliggende værdi i en særskilt type.
  • De afhjælper brugen af primitive typer og forhindrer forveksling af argumenter.
  • Brug case class for enkelhed og opaque type uden omkostning.
  • Tilføj en smart konstruktør til validerede indpakninger.
  • Konverteringer mellem newtypes er altid eksplicitte.
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)
Gratis at komme i gang

Lær Scala med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
39
Lektioner
143

Ofte stillede spørgsmål

Er lektionen “Newtypes” gratis?

Ja — hele teksten til “Newtypes” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af Scala til backendudvikling og funktionel programmering-kurset, skal du opgradere til CoddyKit PRO. Scala til backendudvikling og funktionel programmering-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Newtypes”?

Typesikre wrappers Du øver dig i Scala til backendudvikling og funktionel programmering med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på Scala til backendudvikling og funktionel programmering?

Der kræves ingen tidligere erfaring. Scala til backendudvikling og funktionel programmering på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 3 af 4.

Hvor lang tid tager lektionen “Newtypes”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne Scala til backendudvikling og funktionel programmering-lektion?

Ja. Alle Scala til backendudvikling og funktionel programmering-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Modellering med ADT'er
  2. Smarte konstruktører
  3. Newtypes
  4. Sammensætning af domæner
← Tilbage til Scala til backendudvikling og funktionel programmering