A Sather programozási nyelv

Típusok, típuskonstrukciók



„Hagyományos” típuskonstrukciós eszközök a nyelvben nincsenek, ezekre a standard könyvtárakban levő absztrakt osztályok nyújtanak lehetőséget.

Felsorolási típusok

Satherben felsorolási típus közvetlenül nem készíthető, ami szerintem egy hiányossága a nyelvnek. Ellenben létezik hozzá egy furcsa kerülőút: a ‘const’ kulcsszóval típus nélkül elnevezett változók sorozata rendre 0-tól egyesével növekvően inicializálódnak (mint a C-beli enum értékek). Például:

const zero, one, two, three, five := 5;

Tömbök

A tömb szerkezetet nem típuskonstrukcióval kaphatunk, hanem az ARRAY{T} paraméteres osztállyal. A tömbelemek indexelése 0-tól kezdődik. Érdekesség, hogy nincs fix mérete a tömbnek – egyszerűen azért, mert a fix méretű tömbökre külön elnevezést használ: n-esek (tuples). Tömböknek is adható kezdeti érték a ‘|’ jel segítségével. Nézzünk néhány példát:

a:ARRAY{INT} := |2,5,7|;            -- Az ’a’ tömböt inicializáljuk 2,5,7 értékekkel #OUT+a[1];                          -- Kiírjuk a tömb második elemét, azaz 5-öt

Adhatunk meg minimális elemszámot készítéskor inicializálás nélkül is:

a:ARRAY{POINT} := #(3); a[0] := #POINT(0.0,0.0);

Ezen kívül vannak referencia tömbök (AREF{T}) és immutable típusú referencia tömbök is (AVAL{T}).

Tömbök egyik tipikus használata a parancssori argumentumok kezelése. Ehhez csak annyit kell tennünk, hogy az alkalmazás main rutinjának adunk egy ARRAY{STR} típusú paramétert.

class MAIN is   main(args: ARRAY{STR}) is     #OUT + "Program name is:" + args[0] + "\n";     #OUT + "First argument:" + args[1] + "\n";     #OUT + "Second argument:" + args[2] + "\n";     #OUT + "Number of arguments:" + (args.size-1) + "\n";   end; end;

Ezen program lefordítása után egy lehetséges futtatás az alábbi kimenetet eredményezheti:

>./prg this that 1 Program name is:prg First argument:this Second argument:that Number of arguments:3

Sather nyelvben is van egy ún. Container osztály a standard program könyvtárban (hasonlóan az Eiffelhez, de itt ez nincs további 3 kategóriára osztva), amelyből nagyon sokféle adatszerkezet (tároló) származik (pl.: verem, sor, lista, zsák, halmaz, prioritásos sor, hash-tábla, stb.)

Osztályok

Ahogy már korábban említettük (ld. 3 fejezet), a Satherben minden egyed (változó) egy objektum, amelyet típusa ír le. Még az egyszerű beépített típusok is osztályok, azonban az ilyen típusú objektumok statikusan jönnek létre, míg az össze többit dinamikusan kell létrehoznunk explicit konstruktor hívások segítségével.

Satherben kétféle típus létezik: konkrét és absztrakt. A konkrét típusokat osztálynak (‘class’) nevezi, míg az absztrakt típusokat egyszerűen típusnak (‘type’ = ’abstract class’). Minden típus nevét csupa nagybetűvel kell írni, az absztrakt osztályok nevét pedig mindig ‘$’ jellel kell kezdeni.

Ebben a fejezetben az osztályokat mutatjuk be részletesebben.

Osztályokat a ‘class’ kulcsszóval hozhatunk létre. Nézzünk erre egy egyszerű példát:

class POINT is   attr x,y:INT;     create(xvalue, yvalue:INT):POINT is     res:POINT := new;     res.x := xvalue; res.y := yvalue;     return res;   end; end;

Egy osztályelemet példányosítani a ‘create’ eljárással lehet (mint C++-ban a new), de Satherben nincs default konstruktor, tehát ha elhagyjuk, akkor az osztály nem példányosítható. A ‘create’-ben a ‘new’ kifejezés szintén új példány létrehozására szolgál, ez foglal területet az objektum számára és visszaadja üresen (mindent void-ra állít). Vegyük észre, hogy mivel nincs paramétere, egyedül a visszatérési értéke határozza meg a típusát. A Sather szemléletnek megfelelően a ‘new’-nak implicit függvénynek kellene lennie, de akkor a szignatúra szerint az overload nem eldönthető. Ez a Sather ‘nagyon erős’ típusosságában már elképzelhető lenne, mivel a visszatérési érték is része a szignatúrának, de csak annyiban, hogy van-e. Ezzel kényszeríteni akarja a programozót a visszatérési érték felhasználására. Azonban a visszatérési érték típusa alapján nem különböztethetünk meg két eljárást. Ha viszont ez egy nyelvi trükk, akkor nem igazán illik bele a nyelv szemléletébe.

Tehát példányosítsuk a fenti osztályt:

p:POINT := POINT::create(3,5);

Talán szebb lenne, ha nem közönséges függvényhívásnak nézne ki a készítés, ezért definiáltak egy ezzel ekvivalens kifejezést is a ‘#’ segítségével:

p:POINT := #POINT(3,5);

Egy változóról a ‘void( )’ függvénnyel lekérdezhető, hogy hivatkozik-e valamilyen objektumra vagy még nem:

a:POINT; ... if void(a) then #OUT + "a is void!" end;

Megjegyzés: ez a teszt nem használható beépített típusokra (pontosabban a változtathatatlan), ugyanis a nulla (0, 0.0, false és NULL) értékre igazat ad vissza. (Minden más változó referencia típusú, tehát a void() függvény hívása ekvivalens a C-ből ismert (a == NULL) kifejezéssel).

Egy osztály a következő összetevőkből áll:

Objektumok tagjait a szokásos pontozással (‘.’) érhetjük el.

Megjegyzés:

Egy osztályon belül minden publikus, hacsak nem mondunk valami mást. Bármit deklarálhatunk sajátnak (private), ekkor csak az osztályba tartozók látják. Ezen kívül egy attribútum lehet csak olvasható (readonly), ekkor kívülről az értékét csak lekérdezni lehet. A láthatósági korlátozás osztály szintű és nem objektum (egyed) szintű, azaz azonos típusú elemek látják egymás ‘szennyesét’ is.

Ezen kívül lehet egy osztály elemeinek osztott (shared) attribútuma, ami tulajdonképpen osztály szintű attribútum. Gyakorlatilag ezek korlátozottan elérhető (csak adott osztály elemei által) globális változók. Ezek láthatóságát ugyanúgy korlátozhatjuk a ‘private’ és a ‘readonly’ kulcsszavakkal.

Konstans attribútumok is minden egyed számára láthatók, de nem módosíthatók. Konstans attribútum csak inicializáláskor vehet fel értéket, tetszőleges konstans kifejezést.

Láthatósági mezők behatárolása a ‘::’ jelöléssel lehetséges.

Bár a memóriakezelésről szemétgyűjtő rendszer gondosodik és nincs szükség felszabadításra,van rá lehetőségünk, hogy explicit módon megszüntessünk objektumokat (destroy kulcsszóval).

Immutable osztályok

Immutable objektumokról már a 3.4. fejezetben beszéltünk. Most tekintsük át, hogy hogyan hozhatunk létre saját immutable osztályokat.

Immutable osztályok definiálásának és használatának módja a legtöbb tekintetben megegyezik a referencia osztályokéval: attribútumokból és ezeken operáló függvényekből állnak. Példaként tekintsük meg a Sather könyvtárban szereplő CPX (komplex szám) immutable osztály egy leegyszerűsített megvalósítását. A kulcspont az, hogy megfigyeljük azt a módszert, ahogyan a create rutinban beállítjuk az attribútumokat.

immutable class CPX is readonly attr real,imag:FLT; create(re,im:FLT):SAME is -- Visszaad egy CPX objektumot, amelyben a real és imag attribútumok -- be vannak állítva a paraméterként kapott értékre res:SAME; res := res.real(re); -- az értékadás másolást jelent! res := res.imag(im); -- az értékadás másolást jelent! return res; end; plus(c:SAME):SAME is -- visszaad egy komplex számot (CPX objektumot): a self és a c összegét return #SAME(real + c.real, imag + c.imag); end; end;

Ahogy már korábban is láttuk, a CPX osztály használható például az alábbi módon:

b:CPX := #(2.0,3.0); d:CPX := #(4.0,5.0); c:CPX := b + d;

A referencia osztályokkal ellentétben az immutable osztályok példányait nem explicit módon a new kulcsszóval hozzuk létre. Egy immutable változóhoz a deklarációtól kezdve mindig van érték hozzárendelve! A fenti példában tehát a create eljárásban a res változót egyszerűen csak deklarálnunk kell. Egy immutable objektum kezdeti értéke úgy van meghatározva, hogy minden adattagja void legyen, és ez lesz az osztály void értéke. Fontos, hogy itt a void érték egészen mást jelent, mint referencia osztályok esetén: nem azt jelenti, hogy az objektum nem létezik, hanem azt, hogy egy speciális értéke van: minden attribútuma void, amely referencia objektumok esetén a NULL pointer, egészek esetén 0, valósak esetén 0.0, logikai értékek esetén pedig false (hamis). (Vagyis egy CPX típusú változó kezdeti értéke egy olyan objektum, amelynek mindkét adattagja 0.0.) Tehát egy void értékű immutable objektum attribútumaira mindig hivatkozhatunk, míg egy void referencia objektum adattagjaira való hivatkozás mindig fatális hibát eredményez.

Az immutable objektumok tehát nem tudják megváltoztatni az értéküket, akkor viszont mit jelent egy attribútumnak való értékadás? Természetesen azt, hogy az objektum lemásolódik, és a megfelelő attribútum értéke módosul. Vagyis a fenti CPX osztály attr real:FLT adattagjának van egy implicit beállító művelete az alábbi szignatúrával:

real(new_real_value:FLT):SAME;

amely az eredeti CPX objektum egy másolatát adja a real adattag módosításával (a create metódusban ezt használtuk). Hasonlítsuk össze ezt egy referencia osztállyal, amelyben ezen művelet szignatúrája az alábbi lenne:

real(new_real_value:FLT);

Fontos továbbá, hogy egy immutable osztály nem tartalmazhat olyan attribútumot, amelynek típusában közvetve vagy közvetlenül szerepel újra ez a típus (pl. referencia osztályoknál egy láncolt lista elemeinek típusa ilyen, ugyanis tartalmaz hivatkozást a lista más elemeire), ugyanis az ebben az esetben nem csak egy referenciát jelentene, hanem lezáratlan, végtelen méretű adatszerkezetet eredményezne.

Absztrakt típus

Satherben lehetőség van az implementációtól való elvonatkoztatásra is. Erre a célra vezették be az absztrakt típus fogalmát, amely valójában egy típusosztályt ír le. Ilyen típusú objektum nem készíthető, ez csupán osztályok felületének (interface) definíciójára alkalmazható. Ez a forma elrejti mind a belső reprezentációt, mind a tárolandó típust. Absztrakt osztályok neve a könnyebb olvashatóság kedvéért mindig ‘$’ karakterrel kezdődik.

Használatukkal klasszikus polimorfizushoz jutunk. Tegyük fel például, hogy van két verem osztályunk (egész értékű elemekkel), az egyik tömbbel, míg a másik listával valósítjuk meg. Ha a megvalósítást láthatatlanná akarjuk tenni, készíthetünk egy absztrakt típust, mely az implementációt elrejti a felhasználó elől. Ez Sather nyelven így nézhet ki:

abstract class $STACK is   create:SAME;   push(e:INT);   pop:INT;   is_empty:BOOL; end;

Ezt az interface-t megvalósító két osztály:

class ARR_STACK > $STACK is ... class LINK_STACK > $STACK is ...

Ez után egy számológépben láthatatlan, hogy melyiket használjuk:

class CALCULATOR is   private attr stack:$STACK;   create(s:$STACK):SAME is res::= new; res.stack:=s; return res; end; end; s:LINK_STACK := #LINK_STACK;   calc: CALCULATOR := #CALCULATOR(s);

(Természetesen még szebb lenne, ha általános verem típusunk lenne. Az ehhez szükséges generikus eszközöket később mutatjuk be.)

Fontos, hogy az absztrakt osztályok csak rutinokból és iterátorokból állhatnak. (Pl. jelen esetben a pop: INT nem egy attribútumot jelent, hanem egy argumentum nélküli rutint!)

E módszer tipikus használata a standard könyvtári alaptípusok megvalósítása: a $STR absztrakt típus a szöveges megjelenítést támogató típusok halmaza. Minden alaptípus ilyen, tehát a $STR altípusai.

Hivatkozás SAME-re csak visszatérési értékként és kimenő paraméterként lehet. Írhatunk ‘create’ eljárást is, bár ezt nem lehet meghívni.

Öröklődés, altípus képzés

Altípus készítése a ‘>’ jellel lehetséges:

abstract class $SUBCLASS > $SUPERCLASS is ...

Ahogy már korábban is említettük, minden típus az $OB típus altípusa. Fontos, hogy altípust csak absztrakt típusból készíthetünk! És van többszörös öröklődés is.

Egy alosztálynak meg kell felelnie a szülő osztály előírásainak. Ezért el kell tudni dönteni, hogy az alosztály szignatúrái megegyeznek-e a szülő osztályéval. Garantálni kell, hogy ha az alosztály szignatúrái megfelelők, akkor jó fog működni.

Sather a kontravariáns megfeleltetés eszközével végzi az összehasonlítást.

Szuper típus

A Sather nyelv egy érdekessége, hogy más nyelvektől eltérően lehetőség van létező típusok fölé rendelni típust. A szupertípusban csak minden altípusában szereplő eljárásokat definiálhatunk, azaz nem bővítheti az interface-t. Például:

abstract class $IS_EMPTY < $LIST, $SET is   is_empty:BOOL; end;