A Concurrent Clean 2.3 programozási nyelv

Utasítások, vezérlési szerkezetek

A Cleanben nincsenek klasszikus értelemben vett utasítások, csak deklarációkat írunk le. A megadott függvények és konstansok halmaza, illetve a startkifejezés alapján a rendszer maga határozza meg a számítási folyamatot, azaz a programgráf redukálása során maga dönti el, hogy mikor, milyen módosításokat hajtson végre az adatokon. A vezérlési szerkezeteknek megfelelő nyelvi elemek sem arra valók, hogy utasítások végrehajtási sorrendjét határozzák meg, hanem csak függvényértékek kiértékelésének sorrendjét szabályozzák. A kifejezések kiértékelési sorrendjét egy, a típusokhoz kötődő jelöléssel befolyásolhatjuk még, erről korábban volt szó.

Értékadás, üres utasítás

Értékadásszerűnek tekinthetjük a függvénydefiníciókban a függvényértékek hozzárendelését, illetve a részszámítások eredményének változóval való megcímkézését. "Többszörös értékadásnak" tekinthetjük azt, amikor egy minta egészét és részeit egyszerre megcímkézzük. Minderre már láttunk példákat. Üres utasításhoz hasonlatos dolog nincs.

A Clean tisztán funkcionális nyelv, aminek jellemzője, hogy nincs előző értéket megsemmisítő értékadásra lehetőség, valamint mellékhatások biztosan nem lépnek fel a nyelvi elemei felhasználása során. További jellemzője a tisztán, modern funcionális nyelveknek a hivatkozási helyfüggetlenség (referential transparency). Ennek a jelentése, hogy ugyanaz a kifejezés a program szövegében mindenhol ugyanazt jelenti. A függvények kiértékelései nem mellékhatásosak, azaz nem változtatják meg a kifejezés értékét, valamint a program változói igazából konstansok. Az utóbbiak értéke esetleg még nem ismertek, de egyértelműek, és a program végrehajtása során nem változhatnak. Ezt az elvet hívjuk egyenlőségi érvelésnek, és ez biztosítja a program funkcionális helyességét.

Szekvencia

Szekveciának tekinthetjük a kompozícióra épülő függvénydeklarációs megoldásokat. Részeredmény kiszámítása történhet blokkban, ennek egy speciális változatában a számítási lépések sorrendjét is előírhatjuk. A blokkban lokális függvényeket, gráfokat (konstansokat) deklarálhatunk, de típusokat nem.

A példában nézzünk egy tipikus lusta-kiértékeléses feladatot, a prímszámok végtelen listájának definícióját:

module block // computation in block import StdEnv primes:: [Int] primes = Erastothenes [2..] where Erastothenes:: [Int] -> [Int] Erastothenes [n:ns] = [n: Erastothenes (filter n ns)] where filter:: Int [Int] -> [Int] filter n [x:xs] | x mod n == 0 = filter n xs | otherwise = [x: filter n xs] Start:: [Int] Start = primes

A programot elindítva addig zúdul elénk a prímszámok áradata, amíg le nem állítjuk:

$ block [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103, 107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211, 223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331, 337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449, 457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587, 593,599,601,607,613,617

Elágazás

Elágazásnak felel meg az esetszétválasztásos függvény, amikor különböző bemenô paraméterértékekre különböző a függvény definíciója. Az esetszétválasztás történhet mintaillesztéssel, amikor a függvény paramétereit különböző mintákkal (tulajdonképpen megcímkézett adatkonstruktorokkal) vetjük össze, vagy egy adott minta ágába írt őrfeltételekkel, ahol logikai értékű kifejezések vizsgálatával választjuk ki a további ágakat.

Két rendezett, egész számokat tartalmazó lista összefűzése egy közös rendezett listába:

module alter // function alternatives import StdEnv combine:: [Int] [Int] -> [Int] combine f [] = f combine [] g = g combine f=:[x:xs] g=:[y:ys] | x < y = [x: combine xs g] | x == y = combine f ys | otherwise = [y: combine f ys] Start:: [Int] Start = combine [2,3,5,7,11,13] [1,3,5,7,9,11]

Mindkét esetszétválasztási módra igaz, hogy akárhány ágat megadhatunk, a mintáknak illetve őrfeltételeknek pedig nem kell diszjunktaknak lenniük. Mindig az első olyan választódik ki, amelyik illeszkedik, és az határozza meg a függvényérték kiszámításának módját (a következő ágra tehát nem csorgunk rá). Utolsóként otherwise kulcsszóval megadható olyan őrfeltétel, amihez ha eljut a vezérlés, mindenképpen kiválasztódik. A minták típusa a függvény paramétereinek típusával megegyező, ezen túl bármilyen lehet. A típus valamennyi értékét nem kell felsorolni; ha a függvénydefinícióban megadott esettér nem teljes, akkor parciális függvényt kapunk. Ilyen függvények esetén a fordító figyelmeztet, illetve ha a lefedett esettéren kívüli értékkel hívjuk meg a függvényt, futási hibát kapunk.

Ezt egy korábbi példával szemléltetjük:

module genlist // generic list type head:: [a] -> a head [x:xl] = x Start:: (Int, Char) Start = (head [1,2,3], head ['abc'])

A head függvényt üres listákra nem definiáltuk. A fordítása:

$ clm genlist -o genlist Compiling genlist Warning [genlist.icl,4,head]: function may fail Generating code for genlist Linking genlist $ genlist -nt (1,'a')

Létezik kétirányú if és többirányú case elágazás is, ezek azonban csak más formát, más szintaxist biztosítanak, az előzőekben bemutatott lehetőségekkel is megvalósíthatóak. Az if elágazásnak mindig van "else" ága, ezért az else hovatartozásának problémája nem lép fel.

Korábbi példánk case-zel és if-fel:

module caseif // function alternatives with case and if import StdEnv combine:: [Int] [Int] -> [Int] combine f g = case (f,g) of (f,[]) -> f ([],g) -> g (f=:[x:xs],g=:[y:ys]) -> if (x < y) [x: combine xs g] (if (x == y) (combine f ys) [y: combine f ys]) Start:: [Int] Start = combine [2,3,5,7,11,13] [1,3,5,7,9,11]

Ciklus

A ciklusnak a rekurzív függvény felel meg. Ezt a megfeleltetést alapul véve válaszolhatunk a vizsgálati szempontokra (bár ez kevéssé fogja jellemezni a nyelv lehetőségeit).

Elöltesztelős ciklus van, azaz a rekurzív függvény egy adott meghívásakor előbb vizsgáljuk meg a feltételt (ami formailag nemcsak logikai értékű kifejezés, hanem illesztendő minta is lehet), azután végzünk az esetszétválasztás megfelelő ágán függvényérték-számítást (ezen belül esetleg a függvény újbóli meghívását). A "ciklusmag" "blokkba van zárva", azaz szintaktikailag elkülönül a ciklus többi részétől.

Korábbi Fibonacci-számos példánk kitűnően illik ide:

module lazy // lazy evaluation import StdEnv fib:: Int -> Int fib 0 = 1 fib 1 = 1 fib n = fib (n-2) + fib (n-1) Start:: Int Start = fib 36

Hátultesztelős ciklus nincs, elágazási lehetőség csak a függvényérték-kiszámítás előtt van. Előre ismert lépésszámú ciklusra külön nyelvi eszköz nincs, nem számítva természetesen azt az esetet, amikor számlálási mechanizmussal valósítunk meg ilyet — ekkor természetesen a ciklusváltozóra, alsó és felső értékre, lépésszámra, iterálásra, változtathatóságra semmilyen megkötés nincs.

module fpcycle // fixed period cycle import StdEnv fib:: Int -> Int fib 0 = 1 fib 1 = 1 fib n = fib (n-2) + fib (n-1) Start:: [Int] Start = map fib [1..36]

A "ciklus változói" (a függvény lokális változói) a függvényen kívül nem hozzáférhetők. Deklarálható általános ciklus, azaz leállási feltételt nem tartalmazó rekurzív függvény. Vezérlésátadó utasítások nincsenek.

Eseményvezéreltség

Van egy ablakozós technikát és felhasználói interakciót megvalósító standard könyvtár, az ez által biztosított eszközök segítségével eseményvezérelten lehet programozni.