Klasy typów i implicity
Naucz się używać klas typów do polimorfizmu ad hoc i wykorzystywać system implicit Scali do tworzenia zaawansowanych abstrakcji.
Klasy typów i implicity to bezpłatna lekcja Scala for Backend Engineering & Functional Programming na CoddyKit. To lekcja 3 z 3. 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 3 lekcji w sumie.
Polimorfizm ad hoc — wyjaśnienie
W Scali polimorfizm oznacza pisanie kodu, który działa z różnymi typami. Poznaliśmy już polimorfizm podtypów oparty na dziedziczeniu, w którym metoda działa dla klasy bazowej i wszystkich jej podklas.
Polimorfizm ad hoc działa inaczej. Pozwala pojedynczej funkcji zachowywać się różnie w zależności od konkretnego przekazanego jej typu, nawet jeśli typy te nie są powiązane dziedziczeniem. Właśnie tutaj klasy typów pokazują swoje możliwości!
Czym są klasy typów?
Klasa typów to wzorzec projektowy, który pozwala dodawać nowe zachowania do istniejących typów bez ich modyfikowania i bez używania dziedziczenia.
- Definiuje kontrakt (trait) dla określonego zachowania.
- Udostępnia „instancje” (obiekty), które implementują ten kontrakt dla różnych typów.
- Wykorzystuje mechanizm implicit języka Scala, aby automatycznie wprowadzać odpowiednie zachowanie do zakresu.
Definiowanie traitu klasy typów
Najpierw definiujemy trait opisujący pożądane zachowanie. Ten trait przyjmuje parametr typu A, który reprezentuje typ, na którym będzie działać nasze zachowanie.
Utwórzmy klasę typów Printable, która potrafi konwertować dowolny typ na czytelny dla człowieka ciąg znaków.
package com.coddykit
trait Printable[A] {
def format(value: A): String
}Tworzenie instancji klasy typów
Teraz musimy powiedzieć językowi Scala, jak uczynić konkretne typy Printable. Robimy to, tworząc implicit objects (lub wartości), które dla każdego typu rozszerzają nasz trait Printable.
Nazywamy je instancjami klasy typów.
package com.coddykit
trait Printable[A] {
def format(value: A): String
}
object PrintableInstances {
implicit object StringPrintable extends Printable[String] {
def format(value: String): String = s"'${value}'"
}
implicit object IntPrintable extends Printable[Int] {
def format(value: Int): String = s"${value}i"
}
}Używanie klas typów z implicit
Aby użyć naszej klasy typów, definiujemy funkcję, która przyjmuje parametr implicit typu klasy typów. Kompilator języka Scala automatycznie wyszuka dostępną niejawną instancję Printable[A] podczas wywołania tej funkcji.
Jeśli ją znajdzie, wstrzyknie ją do funkcji!
package com.coddykit
trait Printable[A] {
def format(value: A): String
}
object PrintableInstances {
implicit object StringPrintable extends Printable[String] {
def format(value: String): String = s"'${value}'"
}
implicit object IntPrintable extends Printable[Int] {
def format(value: Int): String = s"${value}i"
}
}
object PrintableSyntax {
def print[A](value: A)(implicit p: Printable[A]): Unit = {
println(p.format(value))
}
}
object Main {
import PrintableInstances._
import PrintableSyntax._
def main(args: Array[String]): Unit = {
print("hello")
print(123)
}
}Ograniczenia kontekstu: czytelniejsza składnia
Scala oferuje skróconą składnię, nazywaną ograniczeniem kontekstu, dla parametrów implicit. Zamiast (implicit p: Printable[A]) można napisać [A: Printable].
Dzięki temu kod jest czytelniejszy, zwłaszcza gdy mamy wiele parametrów implicit.
package com.coddykit
trait Printable[A] {
def format(value: A): String
}
object PrintableInstances {
implicit object StringPrintable extends Printable[String] {
def format(value: String): String = s"'${value}'"
}
implicit object IntPrintable extends Printable[Int] {
def format(value: Int): String = s"${value}i"
}
}
object PrintableSyntax {
// Using context bound: [A: Printable] is shorthand for (implicit p: Printable[A])
def print[A: Printable](value: A): Unit = {
// To access the implicit instance, use implicitly[Printable[A]]
val p = implicitly[Printable[A]]
println(p.format(value))
}
}
object Main {
import PrintableInstances._
import PrintableSyntax._
def main(args: Array[String]): Unit = {
print("world")
print(456)
}
}Rozszerzanie o własne typy
Siła klas typów polega na tym, że można dodawać nowe zachowania do dowolnego typu — nawet do typów, których nie jesteśmy właścicielami, takich jak Int lub String, a także do własnych typów, na przykład klas przypadków.
Uczyńmy naszą klasę przypadku Person typem Printable.
package com.coddykit
trait Printable[A] {
def format(value: A): String
}
case class Person(name: String, age: Int)
object PrintableInstances {
implicit object StringPrintable extends Printable[String] {
def format(value: String): String = s"'${value}'"
}
implicit object IntPrintable extends Printable[Int] {
def format(value: Int): String = s"${value}i"
}
// New instance for our custom Person type
implicit object PersonPrintable extends Printable[Person] {
def format(person: Person): String =
s"Person(name: ${person.name}, age: ${person.age})"
}
}
object PrintableSyntax {
def print[A: Printable](value: A): Unit = {
val p = implicitly[Printable[A]]
println(p.format(value))
}
}
object Main {
import PrintableInstances._
import PrintableSyntax._
def main(args: Array[String]): Unit = {
val alice = Person("Alice", 30)
print(alice)
}
}Implicit: nie tylko klasy typów
Chociaż klasy typów są jednym z głównych zastosowań, słowo kluczowe implicit w języku Scala jest szerszym mechanizmem. Można go używać do:
- Parametrów implicit: jak w przypadku klas typów — do automatycznego dostarczania wartości.
- Konwersji implicit: do automatycznej konwersji jednego typu na inny (należy używać ich ostrożnie, ponieważ mogą utrudniać śledzenie działania kodu).
- Wartości implicit: do dostarczania wartości domyślnych dla określonych typów, często na przykład kontekstów wykonania.
Najważniejsza idea polega na tym, że kompilator wyszukuje w zakresie odpowiednie definicje implicit.
Zalety klas typów
Klasy typów oferują kilka zalet:
- Rozszerzalność: można dodawać nowe zachowania do istniejących typów bez ich zmieniania.
- Rozdzielenie zależności: definicja typu jest oddzielona od jego zachowania.
- Polimorfizm ad hoc: funkcje mogą działać z dowolnym typem, który udostępnia wymagane zachowanie.
- Testowalność: zachowania można łatwiej testować w izolacji.
- Brak narzutu dziedziczenia: można uniknąć złożoności głębokich hierarchii dziedziczenia.
Szybkie sprawdzenie
Rozważ klasę typów Printable i jej instancje z naszej lekcji. Co się stanie po wywołaniu print(10.5), jeśli założymy, że nie istnieje instancja DoublePrintable?
Podsumowanie i następne kroki
Opanowali Państwo klasy typów i potężny system implicit języka Scala!
- Klasy typów umożliwiają polimorfizm ad hoc, pozwalając definiować zachowania dla typów bez dziedziczenia.
- Składają się z traitu (kontraktu) oraz implicit objects/values (instancji).
- Parametry implicit i ograniczenia kontekstu umożliwiają funkcjom automatyczne znajdowanie i używanie tych instancji.
Klasy typów są podstawą programowania funkcyjnego w języku Scala i są szeroko stosowane w bibliotekach takich jak Cats i ZIO do tworzenia elastycznych oraz niezawodnych aplikacji. Proszę dalej ćwiczyć, aby utrwalić swoją wiedzę!
Często zadawane pytania
Czy lekcja „Klasy typów i implicity” jest bezpłatna?
Tak — pełny tekst „Klasy typów i implicity” 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 3 lekcji w sumie.
Co nauczysz się w „Klasy typów i implicity”?
Naucz się używać klas typów do polimorfizmu ad hoc i wykorzystywać system implicit Scali do tworzenia zaawansowanych abstrakcji. Ć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 3.
Ile czasu zajmuje lekcja „Klasy typów i implicity”?
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.
Wszystkie lekcje w tym kursie
- Typy generyczne i parametry typów
- Wariancja: kowariancja i kontrawariancja
- Klasy typów i implicity