Az Idol programozási nyelv

Objektum-orientált programozás

Mivel az Idol az objektum-orientált nyelvekből ismert lehetőségeket valósít meg, így terminológiája is ezt tükrözi. A kiegészítő típus neve osztály.

Osztályok

Egy osztály szintaxisa a következő:

class foo(field1, field2, field3, …) # foo osztálybeli objektumokhoz való hozzáférést szolgáló eljárások # a foo osztálybeli objektumok inicializációja end

A közönséges Icon eljárások és az osztály objektumait manipuláló eljárások különbözőségét hangsúlyozandó, utóbbiakat metódusoknak nevezzük.

Egy metódus szintaxisa megegyezik egy szokványos Icon-eljárás szintaxisával, például az előbbi foo osztályhoz egy bar nevű metódust a következőképpen definiálhatunk:
method bar(param1, param2, param3, …) # Icon kód, ami hivatkozhat egy foo osztálybeli objektum mezőire end

Mivel egy osztálymetódus végrehajtása mindig az osztály egy adott objektumához kötődik, minden metódus hozzáférhet egy self nevű implicit változóhoz, ami egy rekord az osztálydeklarációban megadott mezőnevekkel.

A self változóra ugyanúgy hivatkozhatunk, mint bármely más rekordra, azaz a dot (.) operátor segítségével.
A metódusok mellett az osztályok tartalmazhatnak megszokott Icon eljárásokat, globális deklarációkat és rekord deklarációkat, standard Icon szemantikával.

Objektumok

A rekordokhoz hasonlóan, egy osztály típus példányai egy konstruktor függvénnyel hozhatók létre, amelynek neve megegyezik az osztály nevével. Egy osztály példányait objektumoknak nevezzük. Egy objektum mezőit inicializálhatjuk explicit módon a konstruktorban, úgy mint a rekordoknál.

Például:

procedure main() f:=foo(1, 2) end

Egy objektum mezőit nem feltétlenül szükséges inicializálni a konstruktorban. Például, ha minden objektum mezőit egyetlen standard értékkel szeretnénk inicializálni, ezt az osztálydeklaráció initially részében tehetjük meg.

Az initially részek speciális paraméter nélküli metódusok, amelyek példányosításkor automatikusan meghívódnak. Az initially szóval kezdődnek, majd azok a sorok következnek, amelyeket az osztály minden egyes objektumának létrehozásakor végre kell hajtani.

Például tegyük fel hogy egy olyan táblázatot kell létrehoznunk, amelyben hozzáférhetőek az elemek a táblába való beszúrás sorrendjében.
Ehhez egy táblázatra és egy listára lesz szükség, mindkettő kezdetben üres:
class taque(L, T) # metódusok, pl. insert, index, foreach… initially $.L:=[] $.T:=table() end


Ilyen esetben objektumok létrehozásánál nem kell paraméterezni a konstruktort:
procedure main() mytaque := taque end

Ha nem adtunk meg initially részt, a konstruktorban nem megadott argumentumok alapértelmezett értéke null lesz.

Arra is van lehetőség, hogy a mezők egy részét explicit módon a konstruktorban, a többit pedig automatikusan az initially részben inicializáljuk.
Ilyenkor vagy deklarálnunk kell az automatikusan inicializált mezőket a konstruktorban inicializált mezőket követően, vagy pedig &null –t kell megadnunk a konstruktorban az automatikusan inicializált mezők helyén.


Objektum-hivatkozások

A $ operátorral hivatkozhatunk egy adott objektum egy metódusára. A szintaxis a következő:

objektum$metódusnév(argumentumok)

A zárójel üres argumentumlista esetén elhagyható.

Ha egy objektum osztálya ismert, az objektum metódusai szokványos eljáráshívással is meghívhatók:
objektum$metódusnév(argumentumok)
ekvivalens a következővel:
osztálynév_metódusnév(objektum, argumentumok)
ahol az objektum az osztálynév osztály, vagy annak egyik leszármazottjának egy példánya.

Bár az objektum metódusok Icon eljáráshívásokkal is meghívhatók, a $ operátor használatának megvannak az előnyei. Automatikusan kezeli az öröklődést, míg ahhoz hogy az Iconból hívhassunk egy metódust mindig meg kell határoznunk, melyik osztály metódusáról van szó. A $ lehetővé teszi generikus algoritmusok megírását is, amelyek bármilyen osztály objektumát elfogadják, ha az rendelkezik az algoritmusban használt metódusokkal.
A $ használata:
procedure main() mytaque := taque() mytaque$insert("greetings", "hello") mytaque$insert(123) every write(mytaque$foreach) if \ (mytaque$index("hello")) then write (", world") end

Az objektumok ugyan nagyon hasonlítanak a rekordokra, egy objektum mezőihez való közvetlen hozzáférés a szokványos dot (.) operátorral mégsem ajánlatos az adott osztály metódusain kívül, mivel ez megsérti az egységbe zárás elvét. Bár ez megengedett, az Idol rendelkezik egy parancssori opcióval, a –strict –tel , amely ellenőrzi az ilyesfajta hibákat.
A –strict opcióval generált kódban például egy mytaque.L hivatkozás a main() eljárásban futási idejű hibát fog okozni ("invalid field name").

Természetesen az osztály egy metódusán belül az adott osztály mezőihez való hozzáférés elfogadott.

Például:
method insert(x, key) /key :=x put($.L, x) $.T[key] := x end

Tehát a self implicit változó egy rekord és egy objektum is egyszerre.
Mezőihez hozzáférhetünk mint a rekordok mezőihez, ugyanakkor metódusait is meghívhatjuk úgy, mint az objetumok metódusait.

Öröklődés

Gyakran szükség van rá, hogy olyan osztályokat írjunk, amelyek nagyon hasonlítanak egymásra.
Például lehetséges, hogy egy osztály csak kiegészítése egy másik, már definiált osztálynak. Ez jelentheti új mezők, új metódusok hozzáadását, vagy mindkettőt.
Máskor egy osztály csak speciális esete egy másik osztálynak. Például ha már létezik egy tört(számláló, nevező) osztályunk, szükségünk lehet egy inverz(nevező) osztályra is, amelynek viselkedése pontosan ugyanaz mint a tört osztályé, de a számláló értéke mindig 1.
Az Idol mindkét elképzelést támogatja az öröklődés megvalósításával. A szintaxis:

class foo : superclass (fields …) # metódusok # opcionális inicializáló rész end

Az öröklődés szemantikája

Az Idol megengedi, hogy egy osztálynak több szülőosztálya legyen, ezeket az osztálydeklarációban kettősponttal választjuk el.

Egy származtatott osztály olyan rekord típust definiál, amely tartalmazza az osztály definíciójában megadott összes mezőt, és a szülőosztályai definícióiban megadott mezőket. A származtatott osztály metódusai lesznek a definíciójában megadott metódusok, az első szülőosztály metódusai közül azok, amelyek a származtatott osztályban nem voltak definiálva, a második szülőosztály metódusai közül azok, amelyek nem voltak definiálva a származtatott osztályban és az első szülőosztályban stb.
Ez tulajdonképpen egy öröklődési gráf bejárását jelenti.

Az Idolban a többszörös öröklődés a mezőket és metódusokat a mélységi bejárás sorrendjében veszi hozzá a származtatott osztályhoz, mindegyiket csak akkor, ha még nincs vele megegyező nevű mező vagy metódus a származtatott osztályban. Az inicializációs részek is metódusoknak számítanak, és a fent említett módon öröklődnek.

Például:

class inverse : fraction (denominator) initially $.numerator := 1 end


Az öröklődést az Icon a származtatott osztályban nem definiált mezőnevek és metódusok hozzáadásának tekinti, a megszokott objektum-orientált megközelítéssel ellentétben, amely szerint egy származtatott osztály mindig egy szülőosztályból indul, és azt egészíti ki, illetve módosítja a definíciójában. Az Idol így képes olyan osztályokat is definiálni, amelyek kölcsönösen egymás alosztályai. Erről bővebben később lesz szó.

A szülőosztály műveleteinek meghívása

Ha egy alosztály definiál egy metódust amelynek neve ugyanaz, mint egyik szülőosztálybeli metódusáé, akkor a szülőosztály metódusa a következőképpen hívható meg:

objektum$szülőosztály.metódus(paraméterek)

Mivel az inicializáló részek is metódusoknak számítanak, így ezek is hívhatnak szülőosztálybeli metódusokat, többek között a szülőosztály inicializáló részét is.

Publikus mezők

A rekordok és osztályok nagyon hasonlítanak egymásra. Egyszerűbb feladatok esetében a rekordok használata gyorsabb és kényelmesebb, hiszen közvetlenül hozzáférhetünk a mezőihez.

Az osztályok viszont segítenek a kód újrafelhasználásban, és könnyebben áttekinthetővé tesznek nagy programokat. Használatuk elsősorban összetett, vegyes adatszerkezetek esetén ajánlott.

Néha hasznos lehet egy objektum egy mezőjéhez közvetlenül hozzáférni, ahogy a rekordok esetében. Példa erre az Idol fordító által használt metódusokhoz és osztályokhoz tartozó name mező, ami az objektumon kívül használatos string. Egy metódus is visszaadhatná ezt az értéket, de ez nem valami kényelmes megoldás.

Az Idol jelenleg támogatja a read-only hozzáférést egy objektum mezőihez a public kulcsszó használatával. Ha egy osztálydeklarációban a public kulcsszó megelőzi egy mező nevét, az Idol automatikusan generál egy ugyanolyan nevű metódust, ami visszaadja a mező értékét.

Például:

class sinner (pharisee, public publican)
a következő kódot generálja:
method publican() return $.publican end

Ez a funkció amellett hogy hasznos, sajnos lehetővé teszi az egységbe zárás elvének megsértését, hiszen visszaadhat egy változót, aminek mi ezután értéket adhatunk. Az Idol –strict opciója segítségével ellenőrizhetjük, hogy megsértettük-e az egységbezárás elvét.

Ciklikus szülőosztályok és típusekvivalencia

Sok esetben előfordul, hogy egy absztrakt típust többféleképpen is reprezentálhatunk. A különböző reprezentációval megadott típusok ekvivalensnek számítanak. Ennek megadása az Idolban rendkívül egyszerű: legyen a két osztály egymás szülőosztálya.

Például:

class Cartesian : Radian (x, y) # derékszögű koordinátákat használó kód end
class Radian : Cartesian (d, r) # polár koordinátákat használó kód end

A fenti definíció hatására a két típus ugyanazt az objektumtípust fogja jelölni. Az öröklődés után mindkét osztály példányainak lesznek x, y, d és r mezői, és műveleteik is meg fognak egyezni.

Az ekvivalens típusok mindegyikének megvan a saját konstruktora (amelynek neve megegyezik az osztály nevével), és bár ugyanazokat a metódusokat exportálják, a különböző példányokhoz tartozó tényleges eljárások különbözőek lehetnek. Például ha mindkét osztály definiálja a print metódus egy implementációját, akkor egy adott példányhoz az a metódus fog tartozni, amelyik osztály konstruktorával létrejött az objektum.

Ha egy osztály örököl egy metódust valamelyik ekvivalens osztálytól, akkor az ő felelőssége a metódusban használt mezők inicializálása saját konstruktorában, és értékeik frissítése a metódushívások során.

A ciklikus szülőosztályokkal kifejezett ekvivalens típusok gyakorlati haszna egyelőre még kérdéses. De ha másra nem is alkalmasak, legalább kényelmes eszközt adnak ahhoz, hogy több alternatív konstruktort írhassunk egy adott osztályhoz.