5.1. Beépített egyszerű típusok
5.1.1 Integer
5.1.2 Real
5.1.3 Boolean
5.1.4 Char
5.1.5 Real
5.1.6 Null
5.1.7 Any
5.2. Beépített típuskonstrukciók
5.2.1. Homogén kompozit típusok
5.2.1.1. Tömb (array)
5.2.1.2. Sorozat (sequence)
5.2.2. Heterogén kompozit típusok
5.2.2.1. Rekord
5.2.2.2. Struct
5.2.3. Címkézett unió (tagged union)
5.2.3.1. Variant
5.2.3.2. OneOf
5.3 Absztrakt és konkrét típusok
5.4. Equal, similar, copy
A CLU minden adattípusa adatabsztrakciónak tekinthető, ami egy objektumhalmazt és a rajta értelmezett műveleteket jelenti. A CLU-nak vannak beépített típusai, továbbá lehetőséget ad saját absztrakt adattípusok implementálására. A saját és a beépített adattípusok használata teljesen egyforma.
A CLU tervezői 4 osztályra bontották a absztrakt adat-típus műveleteket a megvalósítás is ezt az elvet követi.
A CLU a beépített típusait immutable típusoknak nevezi.
add = proc ( a,b : int ) returns ( int ) signals ( overflow )
sub = proc ( a,b : int ) returns ( int ) signals ( overflow )
mul = proc ( a,b : int ) returns ( int ) signals ( overflow )
div = proc ( a,b : int ) returns ( int ) signals ( zero_divide, overflow )
mod = proc ( a,b : int ) returns ( int )
signals ( zero_divide, overflow )
minus, power, abs, max, min...
from_to_by = iter ( a,b,d : int ) yields ( int )
parse = proc ( s : string ) returns ( int )
signals ( bad_format, overflow )
unparse = proc ( i : int ) returns ( string )
lt = proc ( a,b : int ) returns ( bool )
le, ge, gt, equal, similar, copy...
i2r = proc ( i : int ) returns ( real ) signals ( overflow )
r2i = proc ( r : real ) returns ( int ) signals ( overflow )
trunc = proc ( r : real ) returns ( int ) signals ( overflow )
and = proc ( a,b : bool ) returns ( bool )
or, not. equal, similar, copy
i2c = proc ( i : int ) returns ( char ) signals ( illegal_char )
c2i = proc ( c : char ) returns ( int )
lt, le, ge, gt, equal, similar, copy
size = proc ( s : string ) returns ( int )
empty = proc ( s : string ) returns ( bool )
indexs = proc ( s1,s2 : string ) returns ( int )
indexc = proc ( c : char, s : string ) returns ( int )
c2s = proc ( c : char ) returns ( string )
concat, append
fetch = proc ( s : string, i:int ) returns ( char ) signal ( bounds )
substr = proc ( s : string, from, to : int ) returns ( string )
signal ( bounds, negative_size )
s2ac = proc ( s : string ) returns ( array[char] )
s2sc = proc ( s : string ) returns ( sequence[char] )
ac2s = proc ( a: array[char] ) returns ( string )
sc2s = proc ( s: sequence[char] ) returns ( string )
chars = iter ( s: string ) yields ( char )
lt, le, ge, gt, equal, similar, copy
equal, similar, copy
x:any :=3
y:int := force [ int ]( x )
|
|
|
|
|
|
|
|
|
|
|
|
A CLU-ban az eljárások és az iterátorok is objektumok (!). Átadhatjuk őket eljárások paramétereiként, lehetnek eljárások visszaadott értékei, értékül adhatók változóknak, vagy tárolhatók kompozit objektumok (pl. rekordok, sorozatok, stb. ) komponenseiként. Az eljárások és az iterátorok típusát a fejléc határozza meg. Például a
gcd = proc ( n, d : int ) returns ( int )eljárás típusa
proctype ( int, int ) returns ( int ).
Integereket tartalmazó tömb definíciója:
array[ int ]
array[ array[ int ] ]
Konstruktorok:
new = proc () returns ( array[ T ] )visszaad egy új üres tömböt. Az alsó határ 1, a felső határ 0 lesz. T helyére tetszőleges típus írható. Van egy speciális konstruktor, mely alkalmas nem-üres tömbök létrehozására is. A típusnév megadása után szögletes zárójelekben kell megadni a legkisebb indexet és fel kell sorolni a tömb elemeit. Például:
array [ int ] $ [ 3 : 6 , 17 , 24 , 100 ]Ez létrehoz egy 4-elemü tömböt, melynek a legkisebb indexe a 3 (a legnagyobb indexe a 6), és az elemei sorra 6,17, 24,100. Egyéb műveletek:
size =proc ( a : array[ T ] ) returns ( int )aaz aktuális méret:
low = proc ( a : array[ T ] ) returns ( int )az aktuális alsó indexhatár:
high = proc ( a : array[ T ] ) returns (int)az aktuális felso indexhatárt:
fetch = proc ( a : array[ T ], i : int ) returns ( T )i-edik elem. Ha i az aktuális indexhatárok közé esik, ha nem akkor kivétel váltódik ki, erről bővebben az 5. fejezetben lesz szó
store = proc ( a : array[ T ], i : int, e : T )e elem tárolása a tömb i-edik elemében:
addh = proc ( a : array [ T ], e : T )e elemmel bövíti a tömböt a felsö határon:
addl = proc ( a : array[ T ], e : T )e elemmel bövíti a tömböt az alsó határon:
remh = proc ( a : array[ T ] ) returns ( T )legnagyobb indexu elem:
reml = proc ( a : array[ T ] ) returns ( T )legkisebb indexu elem:
elements = iter (array [T]) yields (T)
indexes = iter (array[T]) yields (int)
Különböző típusoknak lehet ugyanolyan
nevű művelete. A T típus op műveletére való hivatkozása: T$op formával
lehetséges
(pl.: array [ int ] $new).
Az x legyen az a tömb i. eleme ez a sokféleképpen felírható:
x := a[i] <--> x := array[int]$fetch(a,i) a[i] := x <--> array[int]$store(a,i,x)
sequence [ int ]
Konstruktorok:
new = proc () returns ( sequence[ T ] )Sequence típusú objektumokat létrehozhatunk a new művelet segítségével; amit array típusnál megismertünk, de az elemeket inicializáló konstruktort viszont egy kicsit másképp kell használni:
sequence [ int ] $ [ 2, 6 ]Az alsó határt nem kell megadni, mivel értéke mindig l. Egyéb műveletek:
A sequence típusnak is megvannak a fetch, addh, addl, remh, reml műveletei. Ezek a műveletek azonban nem az inputot változtatják meg, hanem új objektumot adnak vissza.
replace = proc ( s : sequence[ T ], i : int, x : T ) returns ( sequence[ T ] )az i. elem x értékre való cseréje (strore helyett)
sequence [ T ] $concata konkatenáció. Ehelyett használható a || operátor szimbólum.
elements = iter (array [T]) yields (T)
indexes = iter (sequence[T]) yields (int)
a2s = proc (array[T]) returns (sequence[T])
s2a = proc (sequence[T]) returns (array[T])
record [ size : int, status : bool ]Konstruktor:
T${ n1 : exprl, ..., nk : exprk }A kapcsos zárójelek között a rekord összes mezőjének szerepelni kell, a neki megfelelő típusú kifejezéssel együtt. Például:
RT$_{ size : 3, status : true }Egyéb műveletek:
get_n = proc ( r : R ) returns ( T ) y : T := get_n ( x ) <--> y : T := x.nLekérdezés:
set_n = proc ( r : R, n : T ) set_n( x, e) <--> x.n := e
struct [ size : int, status : bool ]A definiálásnál a rekordhoz hasonló módon meg kell adni a komponensek neveit, a hozzájuk tartozó típussal együtt , a lekérdezés művelete a recordéval megegyező.
replace_n = proc ( r : R, n : T )Beállítás: A replace művelet egy új objektumot hoz létre, melynek a komponensei megegyeznek az eredeti komponensekkel, kivéve az n nevűt, amely az új komponenst tartalmazza.
variant[ nl : T1, ..., nk : Tk ]Konstruktor:
make_n = proc ( t : T ) returns ( V )Egy variant típusú típus készítése, ami létrehozza az objektumot. Egyéb műveletei:
change_n = proc ( v : V, t : T )Módosítás. Ez mind az adat és a címkerészt is módosítja, ez biztosítja, hogy címkerészben tárolt információ mindig megfeleljen az aktuális objektum típusának. Ugyanakkor nincs olyan művelet, amely a címkerész ellenőrzése nélkül elérhetővé tenné az adatrészt, mivel az ilyen ellenőrzések során típushibák fordulhatnak elő.
value_n = proc ( v : V ) returns ( T )Az adat lekérdezése:
is_n = proc ( v : V ) returns ( bool )A cimke lekérdezése:
s : V tagcase s tag nl : bodyl tag ni : bodyi tag nj ( tj : Tj) : bodyj tag nk : bodyk end %tagcase
Kompozíció:
A tagcase utasítás végrehajtása során
az aktuális alternatívának megfelelő ág kiválasztódik, az adatrész bemásolódik
az ág-változóba (ha van ilyen, majd végrehajtódik az adott ághoz tartozó
törzs. Ez a művelet akár elhagyható is lenne, mert az is-n
és a value-n műveletek segítségével
megvalósítható lenne a dekompozícióhoz, de ez elegánsabb megoldás.
color = oneof[ red, green, yellow : null ]típus például alkalmas lehet a jelzőlámpa aktuális fényének tárolására (hasonló a más nyelvekben megszokott felsorolástípushoz). Mivel a címke és nem az adat a lényeges, ezért null minden adatrész típusa.
Az adatabsztrakció nem más, mint egy objektumhalmaz és a rajta értelmezett műveletek együttese. Az implementáció során választunk egy reprezentációt az objektumok számára, s ezen reprezentációt használva megadjuk a műveleteket a procedurális absztrakció segítségével. Ha a reprezentációt meg kell változtatni, akkor csak a modulon belül kell átírni a kódot, mivel a program többi része nem függ a reprezentációtól, csak a műveletektől. Mélyebb elméleti alapok a [2]-ben.
Minden CLU implementáció két típussal dolgozik: egy absztrakt adattípussal (abstract type) és egy konkrét adattípussal (rep type). A konkrét típus implementáción kívül nem látható, azaz az objektumok kizárólag absztrakt típusként kezelhetők. Ez egyben azt is jelenti, hogy csak a típusműveletek használhatók. A CLU a típusegyeztetés során megkülönbözteti az absztrakt és a konkrét típust. A CLU-ban a típus külön modulként implementálható. Az adatabsztrakció modulja a cluster (ennek az első három betűjéből kapta a nevét a nyelv). Egy cluster a következőkből áll:
data_name = cluster is % list of names of operations rep = % description of representation % implementations of the operations % some helping routines end data_name
down = proc ( d : data_name ) returns ( rep ) up = proc ( r : rep ) returns ( data_name )A down és az up operátorok csak a clusteren belül használhatók. Mivel kívülről nem elérhetők, nem alkalmasak a CLU típusellenörző mechanizmusának megkerülésére.
A + és a - műveletekkel ellátott komplex számok típusa például lehet a következő:
complex = cluster is create, get_re_part, get_im_part, set_re_part, set_im_part, add, sub % description of representation rep = record[ re : real, im : real ] % implementation of the operators create = proc () returns ( complex ) return ( up( rep$_{0,0} ) ) end create get_re_part = proc ( c : complex ) returns ( real ) return ( rep$get_re( down( c ) ) ) end get_re_part get_im_part = proc ( c : complex ) returns ( real ) return ( rep$get_im( down( c ) ) ) end get_im_part set_re_part = proc ( c : complex, rp : real ) rep$set_re( down( c ), rp ) end set_re_part set_im_part = proc ( c : complex, ip : real ) rep$set_re( down( c ), ip ) end set_im_part add = proc ( cl : complex, c2 : complex ) returns ( complex ) return ( up( rep$_{ re : rep$get_re( down( cl ) ) + rep$get_re( down( c2 ) ), im : rep$get_im( down( cl ) ) + rep$get_im( down( c2 ) ) } ) ) end add sub = proc ( cl : complex, c2 : complex ) returns ( complex ) return ( up( rep$_{ re : rep$get_re( down( cl ) ) - rep$get_re( down( c2 ) ), im : rep$get_im( down( cl ) ) - rep$get_im( down( c2 ) ) } ) ) end sub end complexGyakran előfordul az az eset, hogy a művelet - mielőtt valamit is csinálna - először az átvett absztrakt objektumokat konkréttá alakítja (meghívja a down műveletet), majd elvégzi a megfelelő változtatásokat, s végül a visszatérési érték megadásánál meghívja az up műveletet. Ezeknek az eseteknek a kényelmesebb kezelésére a cvt kulcsszó használata ad lehetőséget. A műveletek fejlécében (mind az argumentumlistában, mind az eredménylistában) szerepelhet a cvt, mint típusnév. Argumentumtípusként való használata azt jelenti, hogy az aktuális paraméternek absztrakt típusúnak kell lennie, míg a formális paraméter konkrét típusú. Hatására az aktuális paraméterre közvetlenül a hívás után implicit módon meghívódik a down művelet, és a kapott konkrét típusú objektum értékül adódik a formális paraméternek. Ha a cvt az eredménylistában szerepel az azt jelenti, hogy az eredmény-objektum absztrakt típusú, míg a visszaadott objektum konkrét típusú. A visszaadott objektumra implicit módon végrehajtódik az up művelet közvetlenül mielőtt visszaadódik.
Az előbbi komplex típus implementációja elegánsabb a cvt használatával:
complex = cluster is create, get_re_part, get_im_part, set_re_part, set_im_part, add, sub % description of representation rep = record[ re : real, im : real ] % implementation of the operators create = proc () returns ( cvt ) return ( rep$new() ) end create get_re_part = proc ( c : cvt ) returns ( real ) return ( rep$get_re( c ) ) end get_re_part get_im_part = proc ( c : cvt ) returns ( real ) return ( rep$get_im( c ) ) end get_im_part set_re_part = proc ( c : cvt, rp : real ) rep$set_re( c, rp ) end set_re_part set_im_part = proc ( c : cvt, ip : real ) rep$set_im( c, rp ) end set_im_part add = proc ( cl : cvt, c2 : cvt ) returns ( cvt ) return ( rep$_{ re : rep$get_re( cl ) + rep$get_re( c2 ), im : rep$get_im( cl ) + rep$get_im( c2 ) } ) end add sub = proc ( cl : cvt, c2 : cvt ) returns ( cvt ) return ( rep$_{ re : rep$get_re( cl ) - rep$get_re( c2 ), im : rep$get_im( cl ) - rep$get_im( c2 ) } ) end sub end complexA down, up operátorok és a cvt kulcsszó használata nem okoz generált kódbeli változást. Csak arra szolgálnak, hogy megváltoztassuk a nézőpontunkat egy adott objektumra vonatkozóan. Erre szükség is van, mivel a fordító típusellenőrzést végez, és az absztrakt és konkrét típus különbözik egymástól az absztrakciós szintet tekintve. A típusellenőrzés fordítási időben történik, így a típusinformációra nincs szükség a generált kódban.
Az absztrakcióhoz hozzátartozik a megváltoztathatóság (mutability) vagy megváltoztathatatlanság (immutability), amelyet következetesen kell kezelni az implementáció során. Egy mutable absztrakciónak a reprezentációja is mutable kell hogy legyen, különben nem valósulhatna meg a változtathatóság. Viszont az immutable absztrakció esetén nem követelmény az immutable reprezentáció. A műveletek megfelelő implementációja biztosíthatja az immutable tulajdonságot.