Newtype
Bezpieczne typowo opakowania
Newtype to bezpłatna lekcja Scala for Backend Engineering & Functional Programming na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Scala for Backend Engineering & Functional Programming, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Scala for Backend Engineering & Functional Programming zawiera 4 lekcji w sumie.
Bezpieczne typowo opakowania
Newtype to odrębny typ opakowujący pojedynczą wartość bazową. Zapobiega mieszaniu wartości o tej samej reprezentacji prymitywnej, takich jak UserId i ProductId, które są typu 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 differProblem obsesji na punkcie prymitywów
Obsesja na punkcie prymitywów polega na nadmiernym używaniu String i Int do reprezentowania pojęć domenowych. Pozwala to na bezsensowne sytuacje, takie jak przekazanie adresu e-mail w miejscu, w którym oczekiwane jest imię. Newtypes rozwiązują ten problem, nadając każdemu pojęciu własny 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 w case class
Najprostszym newtype jest jednopólowa case class. Zapewnia ona równość, czytelną metodę toString i dopasowywanie wzorców od razu po utworzeniu. Kosztem jest utworzenie jednego obiektu dla każdej wartości.
case class OrderId(value: String)
object Main:
def main(args: Array[String]): Unit =
val id = OrderId("ORD-42")
println(id)
println(id.value)Newtypes jako typy niejawne
Aby uniknąć tworzenia obiektów, zaimplementuj newtype za pomocą typu niejawnego. W czasie działania zachowuje się jak typ bazowy, ale w czasie kompilacji jest odrębnym typem.
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)Dodawanie zachowania za pomocą rozszerzeń
Udostępniaj operacje dla newtype za pomocą metod extension, wystawiając tylko te, które mają sens dla danego pojęcia domenowego.
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 w sygnaturach funkcji
Newtypes sprawiają, że sygnatury same dokumentują swoje znaczenie, i wykrywają błędy kolejności argumentów w czasie kompilacji. Nie można przypadkowo zamienić dwóch parametrów o różnych 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 i kolekcje
Map, którego kluczem jest newtype, jest czytelniejszy i bezpieczniejszy niż mapa z kluczem typu raw Int. Typ uniemożliwia użycie niewłaściwego rodzaju klucza.
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)))Zwalidowane newtypes
Połącz newtype z inteligentnym konstruktorem, aby opakowanie również wymuszało niezmienniki. NonEmptyString nie może być wtedy pusty.
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(""))Wybór reprezentacji
Wybierz rozwiązanie zależnie od potrzeb.
- case class: najprostsza, obsługuje dopasowywanie wzorców i tworzy obiekt.
- opaque type: brak kosztu, brak boxingu, idealny dla często wykonywanych ścieżek i dużych kolekcji.
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)Zapobieganie przypadkowym konwersjom
Dwa newtypes oparte na tym samym typie bazowym nie konwertują się niejawnie. Aby przejść między nimi, należy napisać jawną funkcję, dokumentującą intencję.
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)))Kiedy używać newtypes
Sięgaj po newtypes zawsze, gdy prymityw niesie znaczenie domenowe.
- Identyfikatory, jednostki, ciągi znaków w określonym formacie.
- Używaj case class dla prostoty, a opaque type dla wydajności.
- Dodaj inteligentny konstruktor, gdy występują niezmienniki.
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))Szybkie sprawdzenie
Sprawdź swoją wiedzę na temat newtypes.
Podsumowanie
Poznał(a) Pan/Pani newtypes.
- Newtypes opakowują jedną wartość bazową w odrębny typ.
- Rozwiązują problem obsesji na punkcie prymitywów i zapobiegają pomyłkom argumentów.
- Używaj
case classdla prostoty, aopaque typedla braku kosztu. - Dodaj inteligentny konstruktor do zwalidowanych opakowań.
- Konwersje między newtypes są zawsze jawne.
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)Często zadawane pytania
Czy lekcja „Newtype” jest bezpłatna?
Tak — pełny tekst „Newtype” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Scala for Backend Engineering & Functional Programming, przejdź na CoddyKit PRO. Kurs Scala for Backend Engineering & Functional Programming zawiera 4 lekcji w sumie.
Co nauczysz się w „Newtype”?
Bezpieczne typowo opakowania Ćwiczysz Scala for Backend Engineering & Functional Programming z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Scala for Backend Engineering & Functional Programming?
Nie wymagamy żadnego doświadczenia. Scala for Backend Engineering & Functional Programming w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.
Ile czasu zajmuje lekcja „Newtype”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Scala for Backend Engineering & Functional Programming?
Tak. Każda lekcja Scala for Backend Engineering & Functional Programming zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.