Haskellben az objektum orientált programozás igen hasonlít a módszertani órákon tanultakhoz. Az ember algebrai specifikációt készít a típushoz, azaz leírja az adatmezőket, a műveletek definícióit és a "tételeket".
Egy példa:
module TreeDT (Tree, leaf, branch, cell, left, right, isLeaf) where
data Tree a = Leaf a | Branch (Tree a) (Tree a)
leaf :: a -> Tree a
leaf = Leaf
branch :: Tree a -> Tree a -> Tree a
branch = Branch
cell :: Tree a -> a
cell (Leaf a) = a
left :: Tree a -> Tree a
left (Branch l r) = l
right :: Tree a -> Tree a
right (Branch l r) = r
isLeaf :: Tree a -> Bool
isLeaf (Leaf _) = True
isLeaf _ = False
Mi is derül ki ebből? Az első sorból látható, hogy a Haskellben a láthatósági viszonyokat a modulszerkezettel kell megadni. Egy modul megmondhatja magáról, hogy milyen műveleteket exportál, a többi rejtve marad. Következmény: ha adott szinten valamit elrejtünk, akkor azt már később nem lehet láthatóvá tenni.
A második sorból jól látszik, hogy az adatok és a műveletek deklarációja - más nyelvektől eltérően - elkülönül egymástól.
A következő sorokban pedig egyszerű függvénydeklarációkat látunk, implementációval. Ezt már ismerjük, nincs benne semmi különös.
Természetesen lehet készíteni absztrakt adattípusokat is. Az előbbi példához tartozó absztrakt adattípus:
data Tree a
leaf :: a -> Tree a
branch :: Tree a -> Tree a -> Tree a
cell :: Tree a -> a
left :: Tree a -> Tree a
right :: Tree a -> Tree a
isLeaf :: Tree a -> Bool
Ezzel egy típust hoztunk létre. A típusok azonban típusosztályokba sorolhatók, amelyeknek példányai típusok. Típusosztály készítése a class kulcsszóval lehetséges. Íme egy egyetlen operátorral rendelkező típusosztály:
class Equality a where
(==) :: a -> a -> Bool
(/-) :: a -> a -> Bool
x /= y = not (x == y)
Ezt úgy lehet értelmezni, hogy definiáltunk egy Equality nevű operátort, amelyet egy a típusra alkalmazva egy olyan típust kapunk eredményül, aminek van == és /= művelete.
Mint látható, ha akarjuk, akkor a típusosztály műveleteit implementálhatjuk is, ekkor ezek - Haskell terminológiával - default műveletek lesznek. Ezt úgy kell érteni, hogy ha egy adott típusosztályba tartozó típus nem implementálja a műveletet, akkor a típusosztály implementációja fog működni.
Tegyük hát bele a Tree típust az Equality típusosztályba, így előállítva a típusosztály egy példányát:
instance (Equality a) => Equality (Tree a) where
Leaf a == Leaf b = a == b
(Branch l1 r1) == Branch (l2 r2)= (l1 == l2) && (r1 == r2)
_ == _ = False
Itt az a típusról megkötöttük, hogy legyen az Equality típusosztályból való.
Természetesen van öröklődés:
class (Equality a) => Ordered a where
(<=) :: a -> a -> Bool
Mi több, van többszörös öröklődés is:
class (Equality a, Show a) => Osztaly a where ...
A névütközéseket úgy küszöbölik ki, hogy azt mondják, ilyet nem szabad csinálni. (Azaz az Eiffelhez hasonlóan?) Lehet átnevezni vagy minősített neveket használni.
Habár eddig azt mutattam be, hogy a típusok, mint a típusosztályok példányai, egészen hasonlóan viselkednek az objektumokhoz. Ettől függetlenül azonban a típusok NEM objektumok.
A Haskell típusrendszerének van egy olyan előnye, hogy a metódusok teljesen biztonságosak típusok szempontjából: ha egy metódust nem a megfelelő típusú adatra próbálunk alkalmazni, akkor az már fordítási időben kiderül.
Összefoglalva:
A Haskell szétválasztja egymástól a típus adatrészének és metódusainak definícióját. Maguk az osztályok a Java interface-éhez hasonlítanak, azaz egy objektum használatának szabályait írják le, nem magát az objektumot.
A Haskell osztályok minden metódusa virtuális.
A Haskellben nincs olyan operator túlterhelés, mint C++-ban, azaz itt különböző típusú függvényeknek nem lehet ugyanaz a neve.
A Haskellben nincs közös ősosztály. (Object, Any vagy ehhez hasonló)
Nincs láthatósági szabályozás. Ez a modulrendszerrel oldható meg.
A többszörös öröklődésből adódó problémákat úgy kerüli meg a Haskell, hogy kikötés, hogy nem lehetnek névütközések, illetve hogy nincsenek pointerek.