A Concurrent Clean 2.3 programozási nyelv

Bevezető

A Concurrent Clean a funkcionális nyelvek családjába tartozik. Hollandiában, a Nijmegeni Egyetemen fejlesztették ki 1987 környékén, a Lean gráfátíró kísérleti nyelvből fejlődött ki. A Concurrent jelzőt fejlődésének csak egy viszonylag késői fázisában kapta, amikor párhuzamossági funkciókkal bővítették, de ez ma még eléggé gyerekcipőben jár. Funkcionális nyelvi képességei viszont teljesek, ha nem is a legteljesebbek a hasonló nyelvekhez viszonyítva. Közel áll a Haskell-hez, de több olyan nyelvi elemet tartalamaz amit a Haskell '98 nem. Sok társával szemben viszont ezen a nyelven a való világgal kommunikáló, interaktív programokat is meg lehet fogalmazni (ami funkcionális nyelvek esetén nem is olyan triviális).

Valamennyi támogatott platformra (Windows, Linux, Mac) natív kódot készít a fordító.

Első példaként nézzük meg, hogyan néz ki Cleanben a világ legnépszerűbb programja:

module hello import StdEnv Start:: String Start = "Hello world!"

melyet a következő parancsokkal fordíthatunk és futtathatunk le:

$ clm hello -o hello Compiling hello Generating code for hello Linking hello $ hello "Hello world!" Execution: 0.00 Garbage collection: 0.00 Total: 0.00

Funkcionális nyelv

Elsőként pár szót ejtenénk a funkcionális nyelvek sajátosságairól, hiszen a későbbieket ennek fényében kell értelmezni, ugyanakkor ez a gondolkodásmód különbözik a hagyományos imperatív, illetve objektumorientált nyelvekétől, és nem biztos, hogy minden olvasó ismeri.

A Clean precízebben megfogalmazva a modern funcionális nyelvek közé tartozik, mivel az összes ezekre a nyelvekre jellemző sajátosságokat megvalósítja (zárójelben a fejezet címe ahol részletesebben van tárgyalva a fogalom):

A funkcionális nyelvekben az adatok, konstansok, kifejezések alapvetően (az elemi értékek kivételével) gráfokként vannak reprezentálva. A gráf minden egyes pontjában egy függvény — például egy operátor — vagy egy adatkonstruktor szimbóluma van, a pontból kivezető irányított élek pedig — ha vannak — az alkalmazott objektumokra mutatnak. Pl. a 3 * 5 kifejezés (ami prefix formában (*) 3 5 alakú) gráfjában három pont található rendre a (*), 3, 5 címkékkel, a (*) pontból irányított él vezet a 3 és 5 pontokba. Az Add (Succ Zero) Zero kifejezés (amely a szokásosan Add (Succ (Zero), Zero) alakban írt kifejezés Cleanbeli formája) gráfjában is három pont van, az Add kettes aritású függvény címkéjétől a Succ-hoz és a Zero-hoz vezet egy-egy él, míg a Succ-tól (melynek aritása egy) a Zero-hoz.

Mit lehet kezdeni ezekkel a gráfokkal? Függvényeket definiálhatunk, melyek tulajdonképpen gráfújraírási szabályok. Az Add függvényt például a következőképpen írhatjuk le:

Add Zero z = z Add (Succ a) z = Succ (Add a z)

A kifejezések kiértékelése azt jelenti, hogy a rendelkezésre álló gráf-újraírási szabályok bal oldalán szereplő mintákat megpróbáljuk illeszteni a gráfjainkra, majd helyettesíteni azt a jobb oldallal. Rövidebben fogalmazva tehát nem más mint átírási lépések sorozata, valamint az ezek során lehetséges redukciók elvégzése. Az átírási lépés során a föggvényt helyettesítjük a függvény törzsével, mindaddig amíg el nem érjük a normálformát. A kiértékelési sorrend/stratégia, azaz a redex-ek (reducible expressions) kiválasztási sorrendje Clean esetén lusta kiértékelésű (lsd. Adattípusok, változók, kifejezések fejezeten belül Kiértékelési sorrend). A lusta kiértékelés mindig megtalálja a normálformát, ha létezik konfluens átíró rendszerekben. Ezt nevezzük egyértelmű normálformának. A konfluens átíró rendszer pedig egy olyan rendszer, amelyben az átírási sorrend nincs hatással a végeredményre, csak arra, hogy normálformára jutunk-e. Ilyen például a lambda-kalkulus.
A fenti példában az Add (Succ a) z minta illeszkedik, az előírt helyettesítés után a Succ (Add Zero Zero) kifejezést kapjuk, melynek gráfja a következőképpen fog kinézni: a Succ címkétől él mutat az Add-hoz, amiből két párhuzamos él vezet ugyanahhoz a Zero ponthoz (az Add mindkét paramétere ugyanaz). Ezt a gráfot tovább lehet alakítani az Add függvény másik mintája alapján, ennek eredményeképpen a Succ Zero kifejezést kapjuk eredményül.

module graph // graph-rewriting system ::MyInt = Zero | Succ MyInt Add:: MyInt MyInt -> MyInt Add Zero z = z Add (Succ a) z = Succ (Add a z) Start:: MyInt Start = Add (Succ Zero) Zero

Már ebben a kis példában is láthattuk, hogy miért jó a gráfreprezentáció: ugyanarra a konstrukcióra több él is mutathat, így a kifejezés leírásánál helyet takaríthatunk meg. Ezen kívül ciklikus struktúrák is létrehozhatók, amelyek a gráfújraírási szabályok segítségével a fent látotthoz hasonló módon alakíthatóak tovább.

module cyclic // cyclic structure import StdEnv Start:: [Int] Start = p where p = [1 : map ((*) 2) p]

Egy Clean program végrehajtása nem más, mint a kezdeti (ún. start-) kifejezésből kiindulva addig végezni a kiértékelési stratégia által diktált sorrendben az átalakításokat, amíg lehetséges. Mindeközben szemétgyűjtés gondoskodik azoknak a gráfdaraboknak a törléséről, amelyekre már nem hivatkozik senki. Az ezután megmaradó gráf a program eredménye.

A graph program futásának eredménye:

$ graph (Succ Zero) Execution: 0.00 Garbage collection: 0.00 Total: 0.00

A -nt kapcsoló eltünteti a végrehajtási idők kijelzését:

$ graph -nt (Succ Zero)

A cyclic program futtatásával a kettőhatványok végtelen listájának kiíratására utasítjuk a gépet:

$ cyclic -nt [1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072,26214 4,524288,1048576,2097152,4194304,8388608,16777216,33554432,67108864,134217728,26 8435456,536870912,1073741824,-2147483648,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ,0,0,0,0,0,0

Csak akkor fogjuk a fenti eredményt látni, ha a program elindítása után azt gyorsan félbeszakítjuk, különben addig fognak a számok szaladni a képernyőnkön, míg le nem állítjuk. A negatív szám és utána a nullák a túlcsordulás miatt kerültek a lista végére.