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.
Spis treści
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class Employee : IEquatable<Employee> { public int Id; public string Name; public Employee(int id, string name) { this.Id = id; this.Name = name; } public bool Equals(Employee other) { return this.Id.Equals(other.Id); } } |
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:
1 2 3 4 5 6 |
module Employee where data Employee = Employee {id :: Int, name :: String} instance Eq Employee where (==) (Employee id1 _) (Employee id2 _) = id1 == id2 |
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:
1 2 3 4 5 6 |
class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool x /= y = not (x == y) x == y = not (x /= y) |
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ć:
1 |
data Employee = Employee {id :: Int, name :: String} deriving (Eq) |
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.:
1 |
data Person = Person String String Int deriving (Eq, Show) |
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:
1 |
class Eq a => Ord a where |
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 wyliczeniowychNum
– opisuje interfejs dla typów liczbowych, a więc m.in. podstawowe operacje arytmetyczneBounded
– definiuje funkcjeminBound
orazmaxBound
, zwracające najmniejszą i największą wartość możliwą do przyjęcia przez dany typShow
– deklaruje m.in. funkcjęshow
, konwertującą wartość na stringaRead
– wymaga implementacji funkcjiread
, 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:
1 |
data Month = January | February | March | April | May | June | July | August | September | October | November | December deriving (Eq, Ord, Show, Read, Bounded, Enum) |
W komentarzu obok każdej linii dodałem informację, z jakiej klasy typów pochodzi używana funkcja:
1 2 3 4 5 6 7 8 9 10 11 12 |
λ: January /= March -- Eq True λ: December > August -- Ord True λ: show February -- Show "February" λ: read "September" :: Month -- Read September λ: maxBound :: Month -- Bounded December λ: succ April -- Enum May |
Linki
- Typeclassopedia – świetne źródło wiedzy o ważnych klasach typów.
- Haskell Part V: Classes, A. Jarombek.
- Haskell/Advanced type classes (WikiBooks).
- Stackoverflow: Difference between OOP interfaces and FP type classes.