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.