A Concurrent Clean 2.3 programozási nyelv

Modulok és hatáskörök

Hatáskörök

A hatáskör a programnak azon része, amelyben a definíciók (pl.: függvény, osztály, makró, típus definíció) a bevezetett tulajdonságokkal (pl.: függvény név, osztály név, osztályváltozó, típuskonstruktor) értelmezve vannak. Két típusa van: lokális (érvényessége, láthatósága egy adott programrészre terjed ki) és globális hatáskör (mindenhonnan elérhető).

Hatásköröket csak a program megadott pontjain szabad bevezetni. Funkcionális nyelvekben jellemzően lokális deklarációk a program szövegében azelőtt vannak elhelyezve, mielőtt egy adott kifejezés használná őket (kedvező alulról felfelé típusú építkezéshez (bottom-up)).

A másik lehetőség a margószabály (off-side rule). Ez a bal oldali margó szélességének változtatása az összetartozó kifejezések csoportosításának azonosítására és deklarációk hatáskörének korlátozására (kedvező felülről lefelé típusú építkezéshez (top-down)). Blokkszerkezet kialakítását eredményezi, mely többek között átláthatóbb kódot biztosít. Cleanben a where kulcsszó után deklarálhatjuk lokálisan függvényeinket:

module margo import StdEnv Szamolas:: Int -> Int Szamolas a = twice (inc a) where inc a = a + 1 twice x = x * 2 Start::Int Start = Szamolas 4
10

A fenti példa a margószabály bemutatásán kívül még egy jellemzője miatt érdekes. Figyeljük meg az inc függvény definicíóját: az ott látható a függvényargumentum nem egyezik meg a Számolás első argumentumával. Viszont ha utobbival szeretnénk számolni a where után, lehetőség van rá. Írjuk át a twice függvényt egy kicsit, és az eredmény változik:

module margo import StdEnv Szamolas:: Int -> Int Szamolas a = twice (inc a) where inc a = a + 1 twice x = a * 2 Start::Int Start = Szamolas 4
8

A twice függvény látja a Szamolas-beli a argumentumot, így tud vele számolni, számára nem is érdekes az inc függvény eredménye. Ellenben az inc függvény-ben nem tudnánk így hivatkozni az első a értékére, mert a definicíójában lévő másik a elfedi azt. Az ilyen jellegű elfedések gyakran okoznak problémákat, ezért ajánlott másképp elnevezni az argumentumot, vagy mint ahogy a következő példa is mutatja, ha lehetőség van rá ne írjuk ki az argumentumot:

module margo import StdEnv Szamolas:: Int -> Int Szamolas a = twice inc where inc = a + 1 twice x= x * 2 Start::Int Start = Szamolas 4
10

A twice függvényben már ki kell írni az x argumentumot, mert az egy másik függvény visszatérési értékétől függ, és csak közvetetten a Szamolas argumentumától.

Hatáskörök egymásba ágyazhatóak: egy új beágyazott hatáskörön belül új definíciók és nevek megadhatóak. Ezek csak ott láthatóak ahol létrehozták őket, illetve az ebbe beágyazott további hatáskörökben, az őt körülvevő hatáskörökben nem lehet hivatkozni rá. Újra lehet definiálni függvényeket és argumentumokat, amelyek is elfedik a korábbi definíciókat. Természetesen minden új beágyazást a margószabály szerint beljebb kell kezdeni, hogy a lexikális elemző megfelően tudja majd értelmezni.

module nested import StdEnv NestedFunc:: Int -> Int NestedFunc a= level1 a where level1 x = level2 (x + 1) where level2 y = level3 (2*y) a where level3 t z = level1 (t+z) where level1 u = u - 1 Start::Int Start = NestedFunc 4
13

Vezessük le, hogy is jön létre a végeredmény:
NestedFunc 4 -> level1 4 -> level2 (4+1) -> level3 (2*(4+1)) 4 [lusta kiértékelésű a Clean, még nem értékeli ki az argumentum értékét. Mivel az a argumentum nincs elfedve, ezért a kezdeti értéket veszi fel] -> level1 (2*(4+1) + 4) -> level1 (2*(4+1) + 4) -1 [a level1 újradeklarációja elfedi a korábbi változatot]
Az utolsó lépés után már elkerülhetetlen lesz az argumentum kiértékelése, megkapjuk az eredményt.

Clean programok moduláris szerkezete

Egy Clean program definíciós és implementációs modulok összességéből áll. Egy implementációs modul és egy definíciós modul megfelel egymásnak, ha ugyanaz a nevük. Az alapötlet az, hogy egy implementációs modulban adott definíciók csak abban a modulban vannak értelmezve, ahol definiálták őket, hacsak nem exportáljuk ezeket a definíciókat azáltal, hogy a megfelelő definíciós modulba tesszük őket. Ebben az esetben a definíciók azokban a modulokban is értelmezettek lesznek, ahol ezeket a definíciókat importáljuk.

Egy végrehajtható Clean program legalább egy implementációs modulból áll, a fő- vagy start modulból, ami egy Clean program legfelső modulja (gyökér modul). Minden Clean modult külön fájlba kell tenni. A modul nevének meg kell egyeznie a fájl nevével, amiben a modult tároljuk. A definíciós modul kiterjesztése .dcl, míg az implementációs modulé .icl. Egy definíciós modulnak legfeljebb egy implementációs modulja lehet. Minden implementációs modulhoz (a főmodult kivéve) kell egy megfelelő definíciós modul.

A fő- vagy start modul

Clean programok kiértékelése a startkifejezés jobb oldalán megadott kifejezésnek a normálformára való hozása. A startkifejezés jobb oldalát tekintjük kezdeti kifejezésnek. Más implementációs modulokban is lehet startkifejezés, ez kényelmes lehet egy ilyen modulban definiált függvény tesztelésekor.

A startkifejezés bal oldala a Start szimbólumból és egy opcionális (*World típusú) argumentumból áll, ami a környezeti paraméter (interaktív alkalmazások írásához szükséges).

Definíciós modulok

Egy implementációs modulban adott definíciók csak abban a modulban értelmezettek, amelyben definiáltuk őket. Ha egy definíciót exportálni szeretnénk, akkor a megfelelő definíciós modulban kell megadni a definíciót. Az ötlet az, hogy elrejtsük az aktuális implementációt a külvilág elől. Ez egyrészt tervezési szempontból előnyös, másrészt egy implementációs modult többször újrafordíthatunk anélkül, hogy bármely más modult újra kellene fordítanunk. Más modulok újrafordítása csak abban az esetben szükséges, ha a definíciós modul is megváltozik, ekkor minden olyan modult újra kell fordítani, amely ettől a modultól függ. Ezért függvények, gráfok és osztálypéldányok implementációja csak implementációs modulban megengedett. Ezeket a definíciós modulban megadott típusdefinícióikkal exportálhatjuk. Amennyiben a típusdefiníció jobb oldalát is rejtve hagyjuk, úgy absztrakt adattípust hoztunk létre.

Definíciók importálása

Az import utasítással lehet definíciós modulok által exportált definíciókat importálni bármely más (definíciós, illetve implementációs) modulba. Az import utasításoknak két fajtája létezik, az explicit, illetve az implicit importok. Egy modul függ egy másik modultól, ha valamit importál a másik modulból. Clean 2.x-ben megengedettek a ciklikus függőségek.

Definíciók explicit importálása

Az explicit importok olyan import utasítások, amelyekben mind a modulok (amelyektől importálunk), mind az importálandó definíciókat jelző azonosítók explicit definiáltak. Minden azonosító, amit explicit importálunk egy definíciós,illetve implementációs modulban, az importáló modul globális hatáskörének részei lesznek. Importálni lehet:

Példa:

implementation module XXX from m import F, :: T1, :: T2(..), :: T3(C1, C2), :: T4{..}, :: T5{field1, field2}, class C1, class C2(..), class C3(mem1, mem2), instance C4 Int

Az import utasítás hatására az m modul által exportált következő definíciókat importáljuk az XXX modulba:

Azonosítók importálása hibaüzeneteket okozhat, mert az importált azonosítók ütközhetnek más, ebben a hatáskörben lévő azonosítókkal. Ez a probléma a belsőleg definiált azonosítók, illetve az importált azonosítók átnevezésével megoldható.

Definíciók implicit importálása

Az implicit importok olyan import utasítások, ahol csak a modul nevét kell megadnunk, ahonnan importálni szeretnénk. Ebben az esetben az összes definíciót importáljuk, amit az a modul exportál (és a modul által importált definíciókat is importáljuk, és így tovább).

Példa implicit importra:

import MyStdEnv

Rendszermodulok és implementációs modulok

A rendszermodulok speciális modulok. Egy rendszer definíciós modul jelzi, hogy a megfelelő implementációs modul egy rendszer implementációs modul, amely nem közönséges Clean kódot tartalmaz. Egy rendszer implementációs modulban megengedett idegen függvények definiálása: ezen idegen függvények törzsei valami Cleantől eltérő nyelven íródtak. A rendszerimplementációs modulok lehetővé teszik interfészek kialakítását operációs rendszerek, fájlrendszerek felé, illetve gyorsítják a végrehajtását pl. összetett adatszerkezetek esetén. Az előredefiniált függvényeket és az aritmetikai, illetve fájl I/O operátorokat jellegzetesen rendszermodulként implementálták.

Rendszer implementációs modulok használhatnak gépi kódot, C-kódot, absztrakt gépi kódot (PABC-kód), illetve minden más nyelven írt kódot. Hogy pontosan mi is megengedett, az az adott Clean fordítótól, illetve a platformtól függ. A code kulcsszó teszi lehetővé, hogy idegen nyelven írjunk Clean programokat.

Ha rendszer implementációs modult írunk, nagyon óvatosnak kell lennünk, mert az idegen függvények helyességét a Clean fordító nem tudja leellenőrizni, így mi leszünk felelősek ezen függvények helyességéért.