Haskellowe klasy typów można porównać do znanych z języków obiektowych interfejsów. Definiują one funkcje, które powinny zostać zaimplementowane dla korzystających z nich typów.

Przykład na start

Zastanówmy się nad dostępnym w języku C# interfejsem IEquatable, pozwalającym na porównywanie implementujących go obiektów.

Klasa Employee implementuje metodę Equals zdefiniowaną przez interfejs IEquatable, dzięki czemu jej obiekty mogą być porównywane w określony przez nas sposób (uwzględniający jedynie ID pracownika). Oto jak wyglądałby podobny kod w Haskellu:

Dlaczego tak to wygląda? Ponieważ klasa Eq definiuje dwie metody: (==) oraz (/=). Wystarczy, że zaimplementujemy dowolną z nich, co z kolei wynika z poniższego kodu, będącego definicją klasy:

Jak widać, obie metody posiadają tutaj swoją domyślną implementację. Nie jest to jednak obowiązkowe dla klas typów i niektóre z nich określają jedynie sygnatury swoich metod.

Deriving

W pewnych przypadkach możliwe jest skorzystanie z typeclass bez konieczności samodzielnego tworzenia implementacji. Dzieje się tak wtedy, kiedy Haskell jest w stanie wygenerować domyślną implementację i spełnia ona nasze potrzeby. Gdybyśmy na przykład chcieli porównywać pracowników, biorąc pod uwagę wszystkie ich pola (nie tylko id, ale również name), moglibyśmy napisać:

Użycie deriving jest możliwe również dla klas typów zdefiniowanych przez użytkownika, ale niezbędne jest włączenie rozszerzenia DeriveAnyClass.

Dziedziczenie

Klasy typów nie mogą po sobie wzajemnie dziedziczyć, ale jeden typ danych może implementować wiele klas. Zarówno przy użyciu deriving, jak i instance, np.:

Dla niektórych myląca może być natomiast notacja z symbolem =>, która może przywodzić na myśl własnie dziedziczenie. Dla przykładu, definicja klasy Ord rozpoczyna się w następujący sposób:

Taki zapis nie oznacza jednak, że każdy typ używający domyślnej implementacji Ord będzie tym samym korzystał z domyślnej implementacji Eq. Oznacza za to, że typ, który może być instancją Ord, musi być również instancją Eq.

Popularne klasy typów

Żeby zyskać wyczucie czym dokładnie są klasy typów i jakie jest ich realne zastosowanie, dobrze jest przyjrzeć się tym, które są zdefiniowane w standardowej bibliotece Haskella. Zapoznaliśmy się już z Eq, a oto kilka kolejnych klas wartych uwagi:

  • Enum– klasa dla typów wyliczeniowych
  • Num – opisuje interfejs dla typów liczbowych, a więc m.in. podstawowe operacje arytmetyczne
  • Bounded – definiuje funkcje minBound oraz maxBound, zwracające najmniejszą i największą wartość możliwą do przyjęcia przez dany typ
  • Show– deklaruje m.in. funkcję show, konwertującą wartość na stringa
  • Read– wymaga implementacji funkcji read, służącej do parsowania stringów na wartość określonego typu

Na koniec zerknijmy jeszcze na prosty przykład, pokazujący jakie korzyści możemy odnieść z użycia powyżej wspomnianych klas typów:

W komentarzu obok każdej linii dodałem informację, z jakiej klasy typów pochodzi używana funkcja:

Linki

.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *