0Pricing
Scala for Backend Engineering & Functional Programming · Lekcja

Wzorzec type class

Polimorfizm ad hoc

Wzorzec type class to bezpłatna lekcja Scala for Backend Engineering & Functional Programming na CoddyKit. To lekcja 1 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.

Czym jest klasa typów?

Klasa typów to wzorzec pozwalający dodawać zachowanie do typów bez ich modyfikowania. Jest to forma polimorfizmu ad hoc: ta sama operacja działa dla wielu niezwiązanych ze sobą typów, a kompilator wybiera właściwą implementację na podstawie typu.

Wzorzec ten pochodzi z języka Haskell i jest szeroko stosowany w bibliotekach Scala, takich jak Cats.

Rozwiązywany problem

Dziedziczenie wymusza przypisanie zachowania do typu już podczas jego definiowania. Często jednak nie można edytować danego typu, ponieważ znajduje się on w bibliotece, albo potrzebne są różne zachowania w różnych kontekstach. Klasy typów pozwalają dołączać zachowanie zewnętrznie.

Krok 1: Zdefiniuj trait

Klasa typów to parametrizowany typem trait. W tym przypadku Show[A] opisuje sposób renderowania dowolnego typu A jako String.

trait Show[A] {
  def show(value: A): String
}

@main def run(): Unit = {
  println("Show trait defined")
}

Krok 2: Udostępnij instancje

Dla każdego obsługiwanego typu konkretnego utwórz instance tego traitu. Są to instancje klasy typów.

trait Show[A] { def show(value: A): String }

object Main {
  val intShow: Show[Int] = (v: Int) => s"Int($v)"
  val strShow: Show[String] = (v: String) => s"Str($v)"

  def main(args: Array[String]): Unit = {
    println(intShow.show(7))
    println(strShow.show("hi"))
  }
}

Krok 3: Użyj instancji

Funkcja przyjmuje instancję jako parametr. Ta sama funkcja render działa dla każdego typu, który ma instancję Show.

trait Show[A] { def show(value: A): String }

object Main {
  def render[A](value: A, s: Show[A]): String = s.show(value)

  val intShow: Show[Int] = (v: Int) => s"<$v>"

  def main(args: Array[String]): Unit = {
    println(render(99, intShow))
  }
}

Uczynienie parametru niejawnym

Ręczne przekazywanie instancji jest uciążliwe. Oznaczenie instancji i parametru jako implicit (lub użycie using w Scala 3) pozwala kompilatorowi dostarczać instancję automatycznie.

trait Show[A] { def show(value: A): String }

object Main {
  implicit val intShow: Show[Int] = (v: Int) => s"<$v>"

  def render[A](value: A)(implicit s: Show[A]): String = s.show(value)

  def main(args: Array[String]): Unit = {
    println(render(42))
  }
}

Polimorfizm ad hoc

Ta sama nazwa funkcji odwołuje się do różnych implementacji na podstawie typu argumentu. Jest to polimorfizm ad hoc, różniący się od polimorfizmu podtypów (dziedziczenia) i polimorfizmu parametrycznego (generyków).

trait Show[A] { def show(value: A): String }

object Main {
  implicit val intShow: Show[Int] = (v: Int) => s"int:$v"
  implicit val boolShow: Show[Boolean] = (v: Boolean) => s"bool:$v"

  def render[A](value: A)(implicit s: Show[A]): String = s.show(value)

  def main(args: Array[String]): Unit = {
    println(render(5))
    println(render(true))
  }
}

Praca z własnymi typami

Na tym polega prawdziwa siła tego rozwiązania: można w przejrzysty sposób dodawać zachowanie do własnych typów, pozostawiając klasę danych wolną od kwestii formatowania.

trait Show[A] { def show(value: A): String }

case class User(name: String, age: Int)

object Main {
  implicit val userShow: Show[User] = (u: User) => s"${u.name} (${u.age})"

  def render[A](value: A)(implicit s: Show[A]): String = s.show(value)

  def main(args: Array[String]): Unit = {
    println(render(User("Ada", 36)))
  }
}

Klasy typów a interfejsy

Interfejs wiąże zachowanie z definicją typu, natomiast klasa typów rozdziela te elementy.

  • Możesz dodać instancję klasy typów do typu, którego nie jesteś właścicielem.
  • Możesz mieć wiele instancji dla różnych kontekstów.
  • Kompilator wybiera zachowanie w miejscu wywołania.

Trzy elementy

Każda klasa typów składa się z trzech części:

  • trait (operacja abstrakcyjna),
  • instancje (implementacje dla poszczególnych typów),
  • interfejs (funkcje wymagające instancji).

Każdy z tych elementów poznasz w kolejnych lekcjach.

Kompletny mały przykład

Połączenie wszystkich elementów wzorca za pomocą generycznej funkcji korzystającej z niejawnej instancji.

trait Show[A] { def show(value: A): String }

object Main {
  implicit val intShow: Show[Int] = (v: Int) => s"#$v"
  implicit val strShow: Show[String] = (v: String) => '"' + v + '"'

  def printAll[A](xs: List[A])(implicit s: Show[A]): Unit =
    xs.foreach(x => println(s.show(x)))

  def main(args: Array[String]): Unit = {
    printAll(List(1, 2, 3))
    printAll(List("a", "b"))
  }
}

Szybkie sprawdzenie

Sprawdź, jak rozumiesz wzorzec klasy typów.

Podsumowanie

Nauczyłeś się wzorca klasy typów:

  • Parametryzowany trait opisuje operację.
  • Instancje implementują ją dla poszczególnych typów.
  • Parametry implicit pozwalają kompilatorowi dostarczać instancję.
  • Zapewnia to polimorfizm ad hoc i pozwala rozszerzać typy, których nie jesteś właścicielem.

Często zadawane pytania

Czy lekcja „Wzorzec type class” jest bezpłatna?

Tak — pełny tekst „Wzorzec type class” 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 „Wzorzec type class”?

Polimorfizm ad hoc Ć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 1 z 4.

Ile czasu zajmuje lekcja „Wzorzec type class”?

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

  1. Wzorzec type class
  2. Definiowanie instancji
  3. Typowe type classy
  4. Wyprowadzanie type class
← Powrót do Scala for Backend Engineering & Functional Programming