A CLU programozási nyelv

Alapvető vezérlési szerkezetek



4. Eljárások, absztrakt eljárások

4.1. Eljárások
4.2. Iterációk


4.1. Eljárások

Az eljárásoknak tetszőleges számú argumentuma és tetszőleges számú eredménye lehet. Generikus eljárások esetén még további paraméterek is előfordulhatnak: változó paraméter típusa csak előredefiniált típus lehet, s értéke az eljáráson belül nem változtatható meg; a típus paraméterre tehetünk megszorításokat is. Az eljárás definíciójának része az eljárás által kiváltható kivételek definícióinak felsorolása is...
 
<eljárásnév> = proc [<generic parms>] ( <formal parms> )
    [returns (<type_spec>,...)] [signals (<exception>,...)] [where (<constaints>,...)]
  <routine_body>
end <eljárásnév>


A CLU a függvényeket olyan eljárásoknak tekinti, amelyek adnak vissza eredményt.

Példa:

s2ac = proc ( s:string ) returns ( array [char] )
indexC = proc ( c:char , s:string ) returns ( int )
new = proc ( ) returns (array [t] )
divide = proc ( num, denom : int ) returns ( int, int )

copy = proc [t:type] ( src:t ) returns ( t )

div = proc ( num, denom : int ) returns ( int )
mod = proc ( num, denom : int ) returns ( int )
Írhatunk olyan div eljárást is, amely megteszi, hogy mind a hányadost, mind a maradékot visszaadja, mivel a függvényeknek több visszatérési értékük is lehet. A visszatérési értékeket a return kulcsszó utáni kifejezés-lista adja meg. Eljárás esetén a return után nem szerepel kifejezés.
divide = proc (num,denom : int ) returns ( int, int )
Ennek segítségével a következőképpen implementálhatjuk a legnagyobb közös osztót kiszámoló programot:
gcd = proc ( n, d : int ) returns ( int )
  % legnagyobb közös osztó
  if d = 0 then
    return ( n )
  else
    quotient, remainder : divide( n, d )
    return ( gcd( d, remainder ) )
  end %if
end gcd
Az egy eredményt visszaadó eljárások kifejezésekben is használhatók:
x : int := gcd( 10, 3 ) + 7
A paraméter nélküli eljárások esetén a zárójelpárt ki kell tenni annak ellenére, hogy nincs átadott paraméter:
a : array[int] := array[int]$new( )
x, y : int := divide( 3, 4 )
Eredményt visszaadó eljárások is hívhatók utasításként. Ekkor a visszaadott objektum automatikusan figyelmen kívül hagyódik.   Eljáráshíváskor a következő történik:
  1. Az argumentum-kifejezések kiértékelődnek balról jobbra, és létrejönnek az argumentum objektumok.
  2. Változók jönnek létre a formális paraméterlistának megfelelően.
  3. Az argumentum objektumok értékül adódnak a létrejött változóknak.
  4. Az eljárástörzs kiértékelődik. Ha a törzs terminál, visszaadódnak az eredmény objektumok (ha van ilyen), és felszámolódnak a létrehozott formális és lokális változók.
A CLU paraméterátadása megosztás szerinti (call by sharing), azaz az argumentum objektumot a hívó és a hívott eljárás osztottan használja, feltéve, hogy az mutable. Ha egy ilyen osztott objektumot a hívott eljárás megváltoztatja, a változás a hívó számára is látható lesz.
Másképp megfolgalmazva: ha a formális paraméter mutable és az aktuális paraméter (mutable) változó, akkor cím szerinti, egyébként pedig érték szerinti paraméterátadás történik. (Ha a formális paraméter mutable és az aktuális paraméter kifejezés, akkor is tulajdonképpen cím szerinti paraméterátadás történik az éppen létrehozott objektummal.) A paraméter átadásának módját külön megadni nem lehet.
 

4.2. Iteráció-absztrakció

Az iteráció-absztrakció (röviden iterátor) nem más, mint a programozási nyelvek többségében használható iterációs módszer egyfajta általánosítása. Ennek az absztrakciónak akkor vehetjük igazán hasznát, ha például valamilyen típus összes értékére végre szeretnénk hajtani valamilyen adott műveletet. Az iterátorok az eljárásokhoz hasonlóan hívhatók, és nekük is lehet több visszatérő értékük.

Az iterációt általában egy adott (összetett) típusú változó elemeinek felsorolására használjuk. Ezeket az értékeket azonban nem egyszerre, hanem egyesével lehet kinyerni, s visszaadásukra nem a return, hanem a yield utasítás szolgál. Az előállított elemek használhatók más modulokban, ahol a következő séma szerint dolgozhatjuk fel őket:

Minden i elemre, amit az A iterátor előállított: hajtsunk végre egy műveletet az i-n. Valahányszor az iterátor visszaad egy elemet a yield utasítás segítségével, a visszaadott elem feldolgozódik a ciklus törzsében előírt módon, majd a vezérlés az iterátorban folytatódik, hogy egy újabb elemet adhasson vissza. Tehát az iterátor felelős az elemek generálásáért, míg a hívó modul adja meg, hogy az elemekre milyen műveletet kell végrehajtani.

Az iterátorok implementálására a CLU nyelvben az iter modul szolgál, melynek definiálása a következőképpen történik:

<iterátornév> = iter [<generic parms>] <formal parms>
    [yields (<type_spec>,...)] [signals (<exception>,...)] [where (<constaints>,...)]
  <routine_body>
end <iterációnév>
Egy iterátor az iteráció minden egyes lépésében nulla, egy vagy több elemet bocsáthat ki. Ezen objektumok típusát a yields után kell megadni (ha az iterációs lépéseknél nem adunk vissza értéket, a yields rész el is hagyható). Az is lehetséges, hogy egy iterátor soha nem ad vissza értéket, azaz a yield utasítás meghívása nélkül terminál. Ez lehetőséget ad arra, hogy az üres adatra (pl. üres halmaz, stb.) vonatkozó műveleteket kivételkezelés nélkül, mégis elegáns módon implementálhassuk.

Az iterátor és az eljárás fogalma nagyon hasonlít egymásra. Az egyetlen fő különbség, hogy az eljárás véget ér a return utasítás hatására, míg az iteráció a yield után még folytatódik, és csak akkor terminál, ha a törzs végére ért a vezérlés.

A CLU-ban a for utasítás segítségével használhatjuk az iterátorokat:

for <declaration list in <iteration call do <body end
Amikor egy for utasításra kerül a vezérlés, létrejönnek a ciklusváltozók (csak akkor, ha szükséges), és az <iteration call végrehajtódik. Ha az iteráció ad vissza eleme(ke)t, a visszaadott elem(ek) a ciklusváltozó(k)hoz rendelődnek, és végrehajtódik a ciklus törzse. Amikor a ciklustörzs futása véget ér, a vezérlés visszaadódik az iterátorhoz.

Ha az iterátor terminál, akkor a for ciklus is terminál, és a vezérlés a ciklust követő utasítással folytatódik. Ha a ciklus törzse olyan utasítást tartalmaz, amely a cilust terminálja, az iterátor automatikusan terminál.

A CLU a beépített típusokhoz tartalmaz beépített iterátorokat. Például az egész típushoz tartozik egy int$from_to iterátor:

from_to = iter ( x, y : int ) yields ( int )
Ez az iterátor felsorolja az x és az y közötti egész számokat (a határokat is beleértve). Az int típusnak van még egy int$from_to_by iterátora is, amelynek a lépésközt is meg lehet adni. A tömböknek (array) és a sorozatoknak ( sequence) két iterátora van: elements (az elemeket adja vissza) és indexes (a legális indexeket adja vissza). A string típusnak van egy chars iterátora, amely felsorolja a string elemeit az elsötöl az utolsóig.

A CLU-ban létezik az iterátor típus, amely alakjában és jelentésében is egyszerű analogonja az eljárástípusnak:

itertype ( ... ) returns ( ... ) signals ( ... )