A Pool programozási nyelv

Felépítés

Koncepció

A Pool párhuzamosságának alapkoncepciója h. minden objektumnak van egy body-ja. A body az egy lokális folyamat amit az objektum párhuzamosan végrehajt más objektumokkal közösen. A body végrehajtásának ideje közben az objektum képes más objektumokkal is kommunikálni.A kommunikáció szinkron üzenetek formájában zajlik, amelyik egy metódushívásból áll (a másik objektum számára). Az objektum amely az üzenetet küldi blokkolódhat addig amíg a fogadó válaszol az üzenetre. Mind a send és a receive hívás explicit utasításokkal történik. Előfordulhat olyan eset h. az objektumoknak nincs meghatározott body-ja.Ez esetben egy default body hajtódik végre amely válaszol minden egyes üzenetre melyek beérkeznek.
Bármikor a végrehajtás alatt új objektumok jöhetnek létre - a rutin amely ezt megteszi a new. A rutin itt egy procedúra fajta ami különbözik a metódustól, ugyanis nincs kapcsolatban egy meghatározott objektummal, hanem hívodhat bármilyen objektum által a rendszerben üzenetek küldése nélkül is. Minden osztálynak van egy rutinja - new - amellyel új objektumokat hozhatunk létre. Az objektumok megszűnése soha nem explicit módon történik (történhet garbage collector segítségével).

Öröklődés és típusképzés

Miért nem építettek öröklődést a POOL2-be? Ehhez nézzük meg az öröklődés és típusképzés kapcsolatát.

Az öröklődés

Konzisztens kód megosztás, a programszöveg könnyebb olvashatósága, az objektumok közti kapcsolatok világosabb kifejezése, és emellett sokkal tömörebb kódot és kevesebb memóriát igénylő programokat biztosít. De az öröklődés több mint kód megosztás. Feltéve, hogy B örököl A-tól, akkor B minden objektuma megfelel A objektumnak. Tehát ahol A-t várunk, ott B is jó. Ezért B-t az A specializált verziójának tekinthetjük. Megengedhetünk többszörös öröklődést is, de problémát okoznak a névütközések.

...és kapcsolata a típusképzéssel.

Az un. statikusan típusos nyelveknél a program szövegéből megállapítható az objektum típusa, s így az altípusok és specializációk kezelhetők. Néhány feltételnek teljesülnie kell, hogy teljesen biztos legyen a típusok megfelelése. Részletesebben: B örököljön A-tól, és legyen m A egy metódusa, amit B újradefiniál. Ha az A-beli m paramétereinek típusa PA1,PA2 ...PAn és eredmény típusa RA míg a B-beli m paraméterei PB1,PB2 ...Pbl és eredménye RB típusúak, akkor teljesülni kell, hogy n=l és PA1* PB1, . . . ,PAn * Pan és RA* RB ahol X*Y azt fejezi ki, hogy X=Y vagy X leszármazottja Y-nak. S bár a statikus nyelvek a típusellenőrzéssel ezen szabályok betartását kikényszerítik, vannak még problémák. Például a névütközések legegyszerűbb megoldási formája, a kötelező átnevezés itt nem alkalmazható, mert az átnevezés nem biztosítja, hogy ha az A osztálynak van m metódusa, akkor az összes tőle öröklőnek is van m metódusa, mert a tőle öröklő átnevezheti azt.

Sokkal komolyabb probléma, hogy néhány esetben szeretnénk kódot megosztani altípus képzése nélkül, és fordítva. Például a Stack osztály implementálásánál a kódot kényelmes lenne az Array osztálytól örökölni. Azt azonban nem akarjuk, hogy a Stack az Array altípusa legyen, mert nem akarjuk az Array összes műveletét megengedni a Stackra. Másrészről a verem különbözőképpen implementálható. Ezek az implementációk teljesen más kódot tartalmaznak, mégsem akarjuk őket különböző típusokként kezelni, mert hasonló funkciót látnak el. Egy másik példában az Int osztályt az Ordering osztály leszármazottjaként szeretnénk látni. Az Ordering csak a teljes rendezést biztosító Less függvényt definiálná. Ennek lehetségesnek kellene lennie, még ha az Int beépített, az ordering pedig programozó által definiált osztály is. Ezért mi azt gondoljuk, hogy a jövő objektum orientált nyelvének meg kell különböztetni az öröklést az típusképzést. Az öröklés az objektum belső struktúrájára vonatkozik, a változókra és a metódusok kódjára. Az típusképzés az objektum külsőleg megfigyelhető tulajdonságait kellene definiálnia. Tehát az elfogadott üzenetek (különösen a metódus neveket és a paraméter típusokat) és az eredmények típusát. Ez a szétválasztás hasonló az absztrakt adattípus implementációjának és interfész definíciójának különválasztásához.

Ebben a jelentésben kellene megkülönböztetnünk az osztályt a típustól. Míg az osztály azokat az objektumokat csoportosítaná, amik ugyanúgy épülnek fel, a típus azokat amiket ugyanúgy lehet használni. Ezen a ponton nem egyszerű meggondolni, hogy az altípusképzés ilyetén jelentését hogyan lehet formalizálni. A specifikációnak olyant kell tervezni ami az objektum viselkedésével van kapcsolatban és nem a belső szerkezetével. Tegyük fel, hogy van ilyen formalizmus. Ekkor minden * típushoz rendelhetünk egy *(x) specifikációt. Tehát azon * objektumokból áll a * amelyekre *(*) igaz. Akkor és csak akkor mondhatjuk, hogy egy A osztály * típusú, ha ***A *(*). Sőt, ha a * típus *(x) specifikációja létezik, akkor az, hogy * altípusa *-nak akkor és csak akkor, ha** *(*) **(*). Külön gondot jelent ennek megvalósítása a POOL2 nyelvben. Gondok jelentkeznek a törzs öröklésekor, ugyanis az öröklés nem igazán megoldható azoknál az objektumoknál, ahol a törzs fontos szerepet játszik.

Speciális nyelvi elemek

A további nyelvi konstrukciók a "szintaktikus cukor" alapötletére alapozva sokkal kényelmesebb és természetesebb módot ad a nyelvvel már egyébként is kifejezhető dolgok leírására. Pl. 3+4 kifejezés a 3!add(4) rövidítése. A POOL2 pontosan megmondja, hogy mivé fejtődik ki az így rövidített kifejezés, így a programozó is használhatja őket saját osztályaihoz. Egyszerűen a kifejtett alakban szereplő metódust kell definiálnia. Ez azt jelenti, hogy a + operátor mindazon osztályokhoz használható amely definiált egykomponensű add metódust. Ezt hívjuk explicit szintaktikus cukornak. Más esetben a tényleges kifejtés el van rejtve a programozó elől, s így csak a rövidítést használhatja. Az objektumok létrehozásához a POOL2 beépített new függvényt tartalmaz, és ez automatikusan része minden programozó által definiált osztálynak. Ezen rutin paramétereit a programozó meghatározhatja, biztosítva az objektum inicializálását. A POOL2 globális nevek definiálására is ad lehetőséget. Egy un. globális menedzser inicializálja ezen objektumokat még a többi objektum elindítása előtt. Már láttuk, hogy vannak metódusok és rutinok a POOL2-ben. Adatvédelmi okokból a metódusok nem adhatók változóknak értékül, nem szerepelhetnek paraméterként vagy eredménykény, csak a rutinok. Sőt új rutinok is születhetnek. A már ismert, osztályokhoz rendelt rutinokon (class routines) kívül lehetséges rutin kifejezéseket írni. Pl.:

ROUTINE (p:int):int TEMP i:int BEGIN i:=p**2-t IF i<0 THEN RESULT 0 ELSE RESULT i FI END


ahol t az ezen rutin kifejezést futtató objektum egy változója. Ezt a rutint a ROUTINE(int):int osztály objektumaként tekinthetjük. A t értéke a rutin objektum létrehozásakor kiértékelődik és onnantól kezdve állandó marad, függetlenül az eredeti t változásától. Ez a rutin objektum is hivatkozáson (referencia) keresztül hívható. Pl. a f(3)-f(5-f(2)) háromszor hívja a az f által jelölt objektumot. A rutin objektumok jól kiegészítik az absztrakt osztályokat. Például new paraméterként adhatók át az objektumok létrehozásakor.

CLASS ST( Key, Info) ROUTINE new ( less: ROUTINE( Key,Key) : Bool ) : ST( Key, Info ) ... END ST


A hatékony végrehajtás érdekében számos standard osztályt biztosít a nyelv, mint például Int és String. Vannak a Smalltalkból már ismert megváltoztathatatlan objektumok, melyek értéke nem változtatható meg, s így ezek az objektumok szabadon másolhatók. Paraméterátadáskor ezek nem hivatkozásként, hanem az osztály másolataként adódnak át. Ilyen például az Int osztály. Természetesen vannak standard unitok például a fájl input- outputra, vagy az operációs rendszer funkcióinak elérésére, vagy például az objektumok processzorokhoz rendelésének szabályzásához. Általában több objektum van egy processzorhoz rendelve, mert az objektumok száma nagyobb, mint a rendelkezésre álló processzorok száma. Ezek az egy processzorhoz rendelt objektumok nem futnak párhuzamosan, hanem ütemezve, egymás után. Továbbá nem mindegy milyen objektumok vannak egy processzorhoz rendelve abból a szempontból, hogy az ugyanazon processzorhoz rendelt objektumok egymás közti üzenetküldéséhez nem kell a hálózatot igénybe venni, ezért ez gyorsabb. Egy objektum létrehozásakor allokációs pragmákkal szabályozhatjuk a processzorválasztást. Például

C.new(par) (* ALLOC HERE, WITH obj , set1 & set2 *)


Így ha lehetséges, akkor az új objektum a létrehozójával lesz egy processzoron, ha ez nem lehetséges, akkor az obj-ban jelöltekkel lesz együtt, ha ez sem lehetséges, akkor a set1 és set2 metszeteként megjelöltek valamelyikére kerül. (set1 és set2 processzorok halmazát jelentik.) Ha több megfelelő processzor is van, akkor a legkevésbé foglalt lesz kiválasztva. Így a programozó és a futtató rendszer együtt dönt; a programozó a program, a futtató rendszer a processzorfoglaltság szempontjait érvényesítve.