Scala för backendutveckling och funktionell programmering · Lektion

Newtypes

Typsäkra omslag

Lektion 3 av 413 steg

Newtypes är en gratis lektion i Scala för backendutveckling och funktionell programmering på CoddyKit. Detta är lektion 3 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för Scala för backendutveckling och funktionell programmering, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Scala för backendutveckling och funktionell programmering innehåller totalt 4 lektioner.

Typsäkra wrappers

En newtype är en separat typ som kapslar in ett enda underliggande värde. Den förhindrar att värden med samma primitiva representation blandas ihop, till exempel UserId och ProductId som båda är 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

Problemet med primitive obsession

Primitive obsession innebär att String och Int överanvänds för domänbegrepp. Det tillåter orimligheter som att skicka en e-postadress där ett namn förväntas. Newtypes löser detta genom att ge varje begrepp en egen typ.

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 enklaste newtype-varianten är en case class med ett enda fält. Den ger likhetsjämförelse, en lättläst toString och mönstermatchning direkt. Nackdelen är en objektallokering per värde.

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 opak typ

För noll allokering kan Ni implementera en newtype med en opak typ. Den fungerar som den underliggande typen vid körning men är separat vid 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)

Lägg till beteende med extension-metoder

Ge en newtype operationer genom extension-metoder och exponera endast det som är meningsfullt för domänbegreppet.

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 självförklarande och upptäcker fel i argumentordningen vid kompilering. Ni kan inte råka byta plats på två parametrar med olika 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 och samlingar

En Map med en newtype som nyckel är tydligare och säkrare än en Map med råa Int som nycklar. Typen förhindrar att fel sorts nyckel används.

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

Validerade newtypes

Kombinera en newtype med en smart konstruktor så att wrappern också upprätthåller invariants. Här kan en NonEmptyString aldrig vara 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(""))

Välj representation

Välj utifrån Era behov.

  • case class: enklast, kan användas i mönstermatchning och allokerar ett objekt.
  • opaque type: ingen kostnad, ingen boxing, idealisk för prestandakritiska kodvägar och stora samlingar.
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)

Förhindra oavsiktliga konverteringar

Två newtypes över samma bastyp konverteras inte implicit. För att gå mellan dem måste Ni skriva en explicit funktion som dokumenterar avsikten.

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

När newtypes bör användas

Använd newtypes närhelst en primitiv typ bär på domänbetydelse.

  • Identifierare, enheter och formaterade strängar.
  • Använd case class för enkelhet och opaque type för prestanda.
  • Lägg till en smart konstruktor när det finns invariants.
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))

Snabbtest

Testa Er förståelse av newtypes.

Sammanfattning

Ni har lärt Er om newtypes.

  • Newtypes kapslar in ett underliggande värde i en separat typ.
  • De löser primitive obsession och förhindrar sammanblandning av argument.
  • Använd case class för enkelhet och opaque type för noll kostnad.
  • Lägg till en smart konstruktor för validerade wrappers.
  • Konverteringar mellan newtypes är alltid explicita.
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 att börja

Lär dig Scala med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
39
Lektioner
143

Vanliga frågor

Är lektionen ”Newtypes” gratis?

Ja – hela texten till ”Newtypes” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i Scala för backendutveckling och funktionell programmering, kan Ni uppgradera till CoddyKit PRO. Kursen i Scala för backendutveckling och funktionell programmering innehåller totalt 4 lektioner.

Vad lär jag mig i ”Newtypes”?

Typsäkra omslag Ni övar på Scala för backendutveckling och funktionell programmering med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig Scala för backendutveckling och funktionell programmering?

Du behöver inga förkunskaper. Utbildningen i Scala för backendutveckling och funktionell programmering på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 3 av 4.

Hur lång tid tar lektionen ”Newtypes”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här Scala för backendutveckling och funktionell programmering-lektionen?

Ja. Varje Scala för backendutveckling och funktionell programmering-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. Modellering med ADT:er
  2. Smarta konstruktorer
  3. Newtypes
  4. Att komponera domäner
← Tillbaka till Scala för backendutveckling och funktionell programmering