Trellis

Típusok

7. Típusok

7.1. Bevezetés

A nyelvben az előre definiált típusok a következők: Integer, Real, Character, String, Boolean, valamint az Array, azaz a tömb típus. A típusok nem a nyelv magjának részei, hanem külön könyvtárban vannak definiálva.

A nyelv a típuskonstrukciós eszközök közül egyedül a tömb létrehozását támogatja, ezen kívül csak osztály definiálására ad lehetőséget. Az osztályt típusnak nevezi, megvalósítására a típusmodul szolgál. Egy típusmodulban pontosan egy típust lehet megvalósítani. A típusok között öröklődési reláció definiálható. A felhasználó által definiált típusok ugyanúgy használhatók a későbbiekben, mint az előre definiált típusok. Az osztály elemei az objektumok. Egyetlen objektumot nem lehet létrehozni, mindenképpen kell hozzá osztályt is definiálni.

Az öröklődés a Trellisben szövegesnek fogható fel, tehát az öröklött műveletek implementációjának programszövegét kell az adott típusmodulba képzelni. Az eljárásban mindig az öröklési láncban legutoljára definiált függvény kerül meghívásra. A többi műveletre minősítetten hivatkozhatunk.

A Trellis nyelvben a specifikáció és az implementáció nem választható külön, a művelettel együtt a törzset is meg kell adni. A fejlesztő környezet feladata, hogy a programozó számára a specifikációkat "kigyűjtse" egy típusmodulból.

Megjegyzés: Előfordulhat, hogy implementáció helyett csak a specifikációt adjuk meg, azaz a törzset elhagyjuk. Fordítani így is lehet a programot, de ha futtatni akarjuk, akkor az összes hivatkozott művelet törzsének szerepelnie kell a típus definíciójában a megfelelő helyen. Ugyanez a komponensekre is érvényes.

Az objektumoknak van statikus és dinamikus típusa. Egy T típusú változónak értékül adható minden olyan objektum, melynek típusa a T leszármazottja. Pl:

var V:T;

var Vsub:Tsub;

V := Vsub;

Ahol Tsub a T típus leszármazottja. Ekkor V statikus típusa T, dinamikus típusa Tsub.

A rendszertípusoknak vannak konvertáló műveleteik, amelyek convert(típus, objektum) formában érhetők el. Az első paraméterben adjuk meg, hogy melyik típus konvertáló műveletét akarjuk meghívni, a másodikban adjuk meg a konvertálandó objektumot.. A saját típusainkhoz mi írhatunk konvertáló műveleteket.

7.2. A típusmodul szintaxisa

7.2.1. Szintaxis

type_definition ::=

type_module típusnév

[ attribute ]

[ definition ]

end type_module

type_generator ::=

type_module típusnév[azonosító:típus,...]

[ attribute ]

[ definition ]

end type_module

attribute

::= constant | mutable

::= abstract | base

::= no_subtypes

::= subtype_of( típus1, ... )

definition

::= operation_definition

::= component_definition

::= fixed_name_definition

::= inherit_definition

::= exclude_definition

7.2.2. A constant / mutable attribútum

A constant attribútum azt mondja, hogy a típus konstans típus. Ez azt jelenti, hogy a típus értékek (objektumok) nem változtathatják meg az értéküket. Ilyen típusok például az Integer, String, stb.

Tegyük fel, hogy V1 egy T típusú változó, T rendelkezik constant attribútummal, és V1 értéke Z. Ekkor a memóriában van egy T típusú objektum melynek értéke Z, és a változó erre az objektumra mutat (a Trellisben a változók referenciák). Ha van még egy T típusú változó, V2, és annak az értéke is Z, akkor az is erre az objektumra mutat. Ha V2 értékét megváltoztatom, például értékül adom neki a W-t, akkor létrehoz egy új T típusú objektumot melynek értéke W lesz, és a referenciát átállítja erre az objektumra.

A rendszer referencia számlálást végez, és ha egy objektumra nem hivatkozik semmi, akkor felszabadítja. Így ha már egyetlen változó sem hivatkozik a Z értékű, T típusú objektumra, akkor felszabadítja azt.

Ha a T típus mutable attribútummal rendelkezik, akkor V1 és V2 különböző objektumokra hivatkozik akkor is, ha azonos az értékük. Értékadáskor az objektum értéke változik meg, nem jön létre új objektum, és nem változik meg a referencia. Alapértelmezés szerint ezt az attribútumot használjuk. A nem-rendszer típusok általában ilyenek, az adattagok módosíthatóak.

7.2.3. Az abstract / base attribútum

Az abstract attribútum azt jelenti, hogy az adott típusnak nem lehetnek objektumai, csak típusleszármazásra használhatjuk. A base attribútum az abstract ellentettje, alapértelmezés szerint ezt használjuk.

7.2.4. A subtype_of és a no_subtype attribútumok

Ez a két attribútum együtt is használható.

A no_subtypes attribútum azt mondja, hogy nem származhatnak le újabb típusok az adott típusból.

A subtype_of(...) attribútum meghatározza, hogy a típus mely más típusokból származik le. A típusok neveit a zárójelben kell felsorolni. A típus ilyenkor örökli az őstípusok komponenseit és műveleteit. Az Object típust nem kell megemlíteni az ősök között, de minden típus leszármazik tőle.

7.2.5. Művelet definíciók

Lásd: 6. Alprogramok

7.2.6. Komponens definíciók

A komponensek a típus adattagjait reprezentálják. Szelekciós jelöléssel hivatkozhatunk rájuk, pl. ha z egy hőmérő objektum, akkor írhatjuk: z.celsius, z.fahrenheit. A szelekciós hivatkozásokat a fordító sajátos módon oldja fel. Az "obj.mezőnév" hivatkozást get_mezőnév(obj) függvényhívásként értelmezi ha értékadás jobboldalán látja, put_mezőnév(obj, value) függvényhívásként, ha értékadás baloldalán látja, és a jobboldal értéke value.

Tekintsünk egy példát, hogy miből mi lesz:

var z:Thermostat;

z.celsius := 5+y; --> put_celsius(z, 5+y);

x := z.celsius-7; --> x := get_celsius(z)-7;

Komponens definiálása:

component me/mytype.kompnév1:típus1

[put láthatóság]

[get láthatóság]

is field;

component me/mytype.kompnév2:típus2

get [láthatóság] is

begin

[...]

return kifejezés;

end;

put [láthatóság] is

begin

[...]

return kifejezés;

end;

Ha me-t használunk, objektum adattagot definiálunk, ha mytype-ot, akkor osztály adattagot.

A komponenseket lehet get_only/put-only attribútummal is definiálni.

A láthatóság lehet: private, public, subtype_visible, allocate_visible. Lásd még: 4.1. Láthatóság és öröklődés.

Az is field azt jelenti, hogy az objektum fizikai reprezentációjában létrejön egy hely, mely ezen komponens értékét tárolja. A get és put műveletek automatikusan generálódnak, de láthatósági szintjük beállítható.

Ha nem field-ként definiáljuk az adattagot, akkor értékét nem tároljuk el (nem történik számára memóriafoglalás), hanem kiszámoljuk. Ebben az esetben meg kell adnunk a get és put műveleteket. A put művelet törzsében a value nevű paraméter felhasználható, mely az értékül adott kifejezés értékét tartalmazza. A típus használója természetesen nem lát semmilyen különbséget a két módszer között.

A Thermostat-nál maradva, a celsius és a fahrenheit komponens definíciója:

component me.celsius: Real is field;

component me.fahrenheit: Real

get is

begin

return me.celsius*(9.0/5.0)+32.0;

end;

put is

begin

me.celsius := (value-32.0)*5.0/9.0;

return me.celsius*(9.0/5.0)+32.0;

end;

A celsius komponenshez automatikusan generált műveletek:

operation get_celsius(me)

returns (Real);

operation put_celsius(me, value:Real)

returns (Real);

7.2.7. Rögzített nevek

A rögzített név egy objektum globális neve. Általában az éppen definiált típus egy eleméről (objektumáról) van szó, melyet az adattagok értékének rögzítésével állítunk elő, és névvel látunk el. Például a true és a false a két logikai érték rögzített neve, és a Boolean típus definíciójának részei. A rögzített nevek globális konstansoknak tekinthetők.

7.2.8. Öröklődési és kizárási definíciók

Egy típus több más típus leszármazottja is lehet, így örökölhet azonos nevű műveleteket. Ekkor egyértelműen meg kell mondani, hogy a most definiált típusban az adott művelet hívása melyik ős azonos nevű műveletének hívását jelentse, különben fordítási hibát kapunk. A típuson belül, a művelettörzsekben minősítetten hivatkozhatunk a többi őstípusban definiált azonos nevű műveletre, így azok sem vesznek el az új típus számára, de a típus felhasználója nem látja őket, így nem is használhatja. A kiválasztásnak két módja van: az inherit definíció kijelenti, hogy melyiket akarjuk örökölni, az exclude definíció pedig kizárja a nem kívánt definíciókat. Ha egyik örökölt definíció sem felel meg nekünk, akkor újra kell implementálni az adott műveletet. Ilyenkor az öröklődés blokkolódik. Ha több azonos néven örökölt műveletre is szükségünk van, új műveletet kell definiálnunk más néven, melynek törzsében meghívjuk az őstípusban definiált műveletet. Ezzel tulajdonképpen átneveztük az örökölt műveletet.

Mivel a komponenseket is műveleteken keresztül érjük el, azonos nevű komponensek esetén a get_xxx, put_xxx műveletek közül is választanunk kell.

A private láthatósági szintű műveletek nem öröklődnek.

7.2.9. A típusgenerátor

A típusgenerátor a típus általánosítása, egy típuscsaládot határoz meg. A típusmodul neve után szögletes zárójelben kell megadnunk a formális paraméterlistát. A típusgenerátor önmagában nem használható típusként, példányosítani kell, azaz megadni, hogy a típuscsalád mely elemét akarjuk használni. Ezt az aktuális paraméterek megadásával érhetjük el. A paraméter típusa Type is lehet, ekkor típust várunk paraméterként. Type a típus típus, hasonló a Smalltalk Class osztályához. A típuscsalád elemeit paraméteres típusoknak nevezzük. A paraméteres típus ugyanúgy használható, mint bármely más típus. Például:

type_module Set[Element_Type:Type]

operation create(mytype)returns(mytype); !letrehozas

operation insert(me, element:Element_Type); !beszuras

operation extract(me) returns(Element_Type); !kevetel

end type_module;

Ez a halmaz típuscsalád. Elemei a valamilyen típusú elemeket tartalmazó halmaz típusok. Az Element_Type paramétert az egész modulban használhatjuk a halmaz elemei típusának azonosítására. A típuscsalád eleme például a Set[Integer], azaz az egészeket tartalmazó halmaz típus. Használata:

var s: Set[Integer];

var i: Integer;

s := create( Set[Integer] );

insert(s, 1);

i := 10+extract(s);