A Haskell programozási nyelv

Objektum-orientált programozás

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.