Eq
osztály származtatásaOrd
osztály származtatásaEgy műveletet szeretnénk több típusra is megvalósítani úgy, hogy az algoritmus függ a típustól. Példák ilyen műveletekre:
Ilyen problémák megoldására valók a Haskell típusosztályok.
A Haskell típusosztályok nem azonosak az objektumorientált programozásbeli osztályokkal. A legfőbb különbség, hogy a típusosztályokban nincsenek adatmezők, csak metódusok. Ezért a típusosztályok inkább a Java interfész fogalmához állnak közel.
Össze lehet adni Int
, Double
, Integer
típusú értékeket, de Char
típusú értékeket nem lehet összeadni.
A Haskellben ezt így fejezzük ki:
-- instance Num Int
-- instance Num Double
-- instance Num Integer
-- -- instance Num Char -- ilyen nincs!
Num a
a típusban az úgynevezett kényszer.
Olyan dolgokat tudunk duplázni, amik összeadhatóak:
Kérdés: A double
visszavezethető (+)
-ra. A (+)
mire vezethető vissza?
Válasz: A (+)
kódja típusfüggő, ezért nem írható le egyetlen általános definícióval, mint a double
. Ezt úgy mondjuk hogy a (+)
a Num
osztály egy metódusa. Egy típusosztály nem más mint metódusok halmaza.
A típusosztály definiálásakor fel kell sorolni az összes metódust. Egy egyszerű osztálydefiníció:
Ebből következően (==)
típusa:
Elnevezések:
Eq
a típusosztály neve, ami tetszőleges nagybetűvel kezdődő szó lehet.a
típusváltozó a típusosztály paramétere. Típusváltozó tetszőleges kisbetűvel kezdődő szó lehet.(==)
a típusosztály metódusa. Metódus lehet operátor, függvény vagy konstans.Lehetőség van többparaméteres típusosztály definiálására is.
Lehetőség van egy típusosztályban több metódus definiálására is.
Figyeljük meg hogy az (==)
típusából nem látszik hogy ez a függvény metódus-e. Ez a kérdés nem is lényeges az (==)
alkalmazása szempontjából.
A példányosításkor definiálni kell az osztály összes metódusát az adott típusra. Egy egyszerű osztálypéldány:
Egy osztálynak egy típusra csak egy példánya lehet. Itt figyelembe kell venni az általános polimorfizmust is, így például egy osztálynak nem lehet példánya [Int]
-re is és [a]
-ra is.
(A GHC egyik kiterjesztése lehetővé teszi az átfedő osztálypéldányokat.)
Definiáljuk az egyenlőségvizsgálatot:
Definiáljuk az egyenlőségvizsgálatot:
Definiáljuk az egyenlőségvizsgálatot:
(Az egyszerűség kedvéért csak néhány nevezett színnel foglalkozzunk.)
Definiáljuk az egyenlőségvizsgálatot a következő adattípusra:
A Preludebeli definíció:
-- class Eq a where
-- (==) :: a -> a -> Bool
-- a == b = not (a /= b)
-- (/=) :: a -> a -> Bool
-- a /= b = not (a == b)
A példány definiálásakor bármelyik metódust definiálhatjuk (mindkettőt is).
Felvetődik a kérdés, hogy a fenti megoldás mivel nyújt többet a következő megoldásnál:
-- class Eq a where
-- (==) :: a -> a -> Bool
-- (/=) :: Eq a => a -> a -> Bool
-- a /= b = not (a == b)
Válasz: Az előző megoldásban megvan a lehetőség a (/=)
függvény definiálására a típustól függően. Az előny még egyértelműbb a következő esetben:
-- class Eq a where
-- (==) :: a -> a -> Bool
-- a == b = not (a /= b)
-- (/=) :: a -> a -> Bool
-- a /= b = not (a == b)
Ha így definiáljuk az Eq
osztályt, akkor a példányosításakor választhatunk, hogy a (==)
vagy a (/=)
metódust definiáljuk; a másik automatikusan definiált lesz. Van lehetőség mindkét metódus definiálására is. (Ha egyik metódust sem definiáljuk, a program kiértékelése végtelen ciklusba kerülhet, mivel a metódusok egymást hívják.)
Az Ord
típusosztály definíciója:
-- class Eq a => Ord a where
-- (<), (<=), (>=), (>) :: a -> a -> Bool
-- x <= y = x < y || x == y
-- x < y = not (x >= y)
-- x >= y = x > y || x == y
-- x > y = not (x <= y)
Az összehasonlításkor feltételezzük, hogy már van egyenlőségvizsgálat.
Ord
alosztálya az Eq
osztálynakEq
ősosztálya az Ord
osztálynakAz Eq a
kényszer használatának az osztálydefinícióban két következménye lesz:
a
típusra definiálhatjuk Ord
példányát, amire már van Eq
példány.Ord a
kényszer magában hordozza az Eq a
kényszertAz alosztály, ősosztály elnevezések összhangban vannak a objektumorientált nyelvekben használt hasonló fogalmakkal. Egy típusosztálynak lehet több ősosztálya is, azaz a többszörös öröklés lehetséges.
Az öröklési hierarchia nem tartalmazhat kört. Ennek nem is lenne értelme, mivel az ilyen esetekben az osztályokat összevonni érdemes:
-- class Mul a => Add a where
-- (+) :: a -> a -> a
-- class Add a => Mul a where -- körkörös öröklés
-- (*) :: a -> a -> a
Ehelyett:
member :: Ord a => a -> T a -> Bool
member a E = False
member a (N l b r)
| a < b = member a l
| a == b = True
| a > b = member a r
Nincs Eq a
kényszer, mégis használhatjuk az (==)
műveletet, mivel Eq
ősosztálya Ord
-nak!
Az Eq
, Ord
, Enum
, Show
, Read
példányosítása rábízható a fordítóra. Ezt az igényünket az adattípus definíciójánál a deriving
kulcsszóval kell jeleznünk:
Eq
osztály származtatásaA származtatott Eq
osztálypéldány a struktúrális egyenlőségvizsgálat. Két kifejezés struktúrálisan egyenlő, ha:
Eq
nem származtatható olyan típusra aminek a definíciójában (->)
szerepel.
Ord
osztály származtatásaA származtatott osztálypéldány a struktúrális rendezés. Számít a típusdefinícióban a konstruktorok sorrendje:
Ha más sorrendben adtuk meg a konstruktorokat:
Többértelműségi probléma: a show (read s)
kifejezésben nem egyértelmű hogy a read
és a show
melyik osztály metódusa.
A többértelműségi probléma feloldása:
vagy
ahol x helyén tetszőleges kifejezés lehet.
A törvények olyan szabályok amit egy példány definiálásakor a programozónak érdemes betartani, de a fordító nem tudja ezeket ellenőrizni.
Példa törvényekre:
Eq
példány legyen ekvivalencia reláció (reflexív, kommutatív, tranzitív).Ord
példány legyen rendezés.read (show
a) ==
a