A CLU programozási nyelv

Eljárások, absztrakt eljárások



5. Beépített adattípusok és típuskonstrukciók

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.

  • Primitív konstruktorok: új objektumot hoznak létre, de nem dolgozzák fel a típusparamétereket.
  • Konstruktorok: új objektumot hoznak létre, feldolgozva a típusparamétereket is.
  • Mutátorok: az objektumot módosító műveletek (értelem szerűen csak mutable objektumok esetében)
  • Obszerverek: az objektumot veszik át paramléterül, de más objektummal térnek vissza
  • 5.1. Beépített egyszerű típusok

    A CLU a beépített típusait immutable típusoknak nevezi.

    5.1.1 Egész típus: Int

    Műveletek:
    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...

    5.1.2 Valós típus: Real

    Rendelkezik az Integer összes műveletével, és a továbbiakkal:
     
    i2r = proc ( i : int ) returns ( real ) signals ( overflow )
    r2i = proc ( r : real ) returns ( int ) signals ( overflow )
    A köznapi értelemben vett kerekítés!
    trunc = proc ( r : real ) returns ( int ) signals ( overflow )
    Lefelé kerekítés

    5.1.3. Logikai típus: Bool

    and = proc ( a,b : bool ) returns ( bool )
    or, not. equal, similar, copy

    5.1.4 Karakter típus: Char

    i2c = proc ( i : int ) returns ( char ) signals ( illegal_char )
    c2i = proc ( c : char ) returns ( int )
    lt, le, ge, gt, equal, similar, copy

    5.1.5 Karakterlánc típus: String

    size = proc ( s : string ) returns ( int )
    empty = proc ( s : string ) returns ( bool )
    indexs = proc ( s1,s2 : string ) returns ( int )
    s1 s2-beli első előfordulásának indexe
    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 )
    Az i.-edik karakter
    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

    5.1.6 Üres pointer típus: Null

    Egyetlen nem módosítható objektuma a nil.
    equal, similar, copy

    5.1.7 Általános típus: Any

    Tetszőleges típusú értéket felvehet. Nincs művelete.
    Használatához bevezették a force kulcsszót: típus-kényszerítés
    force[t] = proctype (any) returns (t) signals (wrong_type)
    x:any :=3
    y:int := force [ int ]( x )
     

    5.2. Beépített típuskonstrukciók

    A CLU többféle kompozit típussal rendelkezik. Vannak köztük homogén típusok (array, sequence ), heterogén típusok (record,struct), valamint ún. címkézett unió -tagged union- (variant,union). Ezeket a következőképp lehet csoportosítani:
         
        mutable
        immutable
        homogén
        array
        sequence
        heterogén
        record
        struct
        címkézett unió
        variant
        union

    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 ).

    5.2.1. Homogén kompozit típusok

    A homogén kompozit típusok, olyan típuskonstrukciók, mely segítségével meghatározott, vagy változó számú ugyanolyan típusú objektumokat foghatunk össze egy objektummá.

    5.2.1.1. Tömb (array)

    A CLU-ban a tömb dinamikus, (a létrehozását követően változtatható a mérete új elem hozzávételével illetve meglevő elem elhagyásával). A nyelv csak egydimenziós tömbök használatát támogatja és az indexek csak egész számok lehetnek. Ha többdimenziós tömböket akarunk használni, tömbök tömbjét kell definiálni. A tömböknek van alsó és felső indexhatára. A két határ közötti bármely egész szám legális indexe a tömbnek (beleértve a határokat is).

    Integereket tartalmazó tömb definíciója:

    array[ int ]
    Integer tömböket tartalmazó tömb definíciója:
    array[ array[ int ] ]
    Amint látszik, az indexhatárok nem képezik a típus részét.

    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 )a
    az 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)
    elem-iterátor
    indexes = iter (array[T]) yields (int)
    index-iterátor


    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)
     

    5.2.1.2. Sorozat (sequence)

    A CLU módszertani nyelv, ezért is van benne sorozat típus is, ami már a mai nyelveknél nem jellemző. Az tömb típusra alkalmazható műveletek többsége a sequence típusnál is használható. A két típus közti különbség annyi, hogy a szekvencia nem módosíthahó típus, és elemeinek indexelése 1-gyel kezdődik.
    Integereket tartalmazó szekvencia definíciója:
    sequence [ int ]
    Az tömb típusra alkalmazható műveletek többsége a sequence típusnál is használható.

    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 ] $concat
    a 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])
    Array to Seq
    s2a = proc (sequence[T]) returns (array[T])
    
    
    

    5.2.2. Heterogén kompozit típusok

    A heterogén kompozit típusok, olyan összetett típusok melyek különböző típusú objektumokat fog össze, ezek lehetnek alaptípusok, de lehetnek már maguk is valamilyen típuskonstrukciók.

    5.2.2.1. Rekord

    Egy rekord típus definiálásához meg kell adni a mezők neveit és típusait. A rekurzív definíció nem megengedett.
    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:
    Egy R rekord n nevű és T típusú mezőihez.
    get_n = proc ( r : R ) returns ( T )
    
    y : T := get_n ( x ) <--> y : T := x.n
    Lekérdezés:
    set_n = proc ( r : R, n : T ) 
    
    set_n( x, e) <--> x.n := e
    Beállítás:

    5.2.2.2. Struct

    A rekord típus nem módosítható megfelelője.
    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.

    5.3.3. Címkézett unió (tagged union)

    Olyan objektumok típusa, melyek mindig egy típusú értéket vehetnek fel a definiált alternatíva-halmazból. Az ilyen típus tehát alternatívák (a variáns típusok) halmaza, melyek mindegyike egy névből azaz cimkéből, és egy típusból áll, hasonlóan a rekordok mezőihez.

    5.3.3.1. Variant

    A variant egy mutable címkézett unió. A típus egy címke- (tag) és egy adatrészből áll. A címkerész valamelyik variáns típus címkéjét 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.

    5.3.3.2. OneOf

    A variant típus immutable változata a oneof. A null típus gyakran használt oneof és variant komponenseként. Ez egy immutable típus, melynek egyetlen objektuma van: a nil.
    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.

    5.3. Absztrakt és konkrét típusok

    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:

      1. Fejléc (a típus és a típusműveletek neve)
      2. A választott reprezentáció megadása
      3. A műveletek implementációja
    A clusteren belül egyéb rutinok (eljárások és iterátorok) szerepelhetnek, de kívülről csak azok a műveletek hívhatók, amik a fejlécben fel vannak sorolva.
    data_name = cluster is
      % list of names of operations 
      rep = % description of representation 
      % implementations of the operations 
      % some helping routines 
    end data_name
    A cluster deklarációjában a rep kulcsszónak kettős szerepe van. Egyrészt tájékoztatja a fordítót arról, hogy az adott adattípus esetén mi a konkrét adattípus. Másrészt mivel egyenlőséggel van definiálva, a clusteren belül a konkrét adattípus helyett rövidítésként használható. A clusteren belül mind a konkrét, mind az absztrakt adattípusnak láthatónak kell lenni. A CLU lehetőséget biztosít az absztrakt és a konkrét adattípusok közti konverzióra. Erre mindenféleképpen rá vagyunk kényszerítve a nyelv szigorú típusossága miatt: a clusteren belül egyszerűen nem tudnánk mit kezdeni az absztarkt típusú paraméterekkel. A down operátor argumentuma egy absztrakt típusú objektum, a visszaadott érték pedig a neki megfelelő konkrét típusú objektum. Az up operátor végzi az ellenkező irányú konverziót. Argumentuma egy konkrét típusú objektum, a visszaadott érték pedig a neki megfelelő absztrakt típusú objektum.
    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 complex
    Gyakran 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 complex
    A 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.

    5.4. Equal, similar, copy

    Minden típus rendelkezik equal és copy műveletekkel, mivel a fordító automatikusan generálja őket, ha a programozó nem ír elő rájuk implementációt. Az egyenlőség operátor nem generálható minden esetben automatikusan, mivel két absztrakt objektum akkor is egyenlő lehet egymással, ha a reprezentációik nem egyenlőek. A másolás általában szintén nem generálható automatikusan, hiszen nem minden esetben egyértelmű, hogy egy objektum miből áll.
    Az equal művelet implementációja a programozóra van bízva. Megfelelő implementáció esetén két mutable objektum pontosan akkor egyenlő, ha teljesen ugyanazok. Ez így természetes, mivel az egyikre alkalmazva egy megváltoztató műveletet, megkülönböztethetjük őket. A similar művelet használható annak eldöntésére, hogy két objektum értéke hasonló-e. A copy művelet szolgál hasonló objektum létrehozására. A lemásolt objektum struktúrájában egyezik az eredetivel, de nem osztozik az eredetivel közös objektumokon. A CLU beépített típusai követik a fenti konvenciókat az equal, similar, copy értelmezését tekintve. Az egységesség és a könnyebb érthetőség érdekében a programozó által definiált típusoknak is ki kell elégíteni az említett követelményeket.