Doktori értekezés. Diviánszky Péter 2012.

Hasonló dokumentumok
FUNKCIONÁLIS PROGRAMOZÁS

FUNKCIONÁLIS PROGRAMOZÁS GYAKORLAT JEGYZET

Funkcionális Nyelvek 2 (MSc)

Kifejezések. Kozsik Tamás. December 11, 2016

2018, Funkcionális programozás

2016, Funkcionális programozás

Java II. I A Java programozási nyelv alapelemei

2019, Funkcionális programozás. 2. el adás. MÁRTON Gyöngyvér

FUNKCIONÁLIS PROGRAMOZÁS ELŐADÁS JEGYZET

Kifejezések. Kozsik Tamás. December 11, 2016

Tulajdonságalapú tesztelés

A szemantikus elemzés helye. A szemantikus elemzés feladatai. A szemantikus elemzés feladatai. Deklarációk és láthatósági szabályok

S2-01 Funkcionális nyelvek alapfogalmai

Programozás II. 2. Dr. Iványi Péter

Memóriagazdálkodás. Kódgenerálás. Kódoptimalizálás

Készítette: Nagy Tibor István

és az instanceof operátor

Java VIII. Az interfacei. és az instanceof operátor. Az interfészről általában. Interfészek JAVA-ban. Krizsán Zoltán

Java II. I A Java programozási nyelv alapelemei

GENERIKUS PROGRAMOZÁS Osztálysablonok, Általános felépítésű függvények, Függvénynevek túlterhelése és. Függvénysablonok

Funkcionális és logikai programozás. { Márton Gyöngyvér, 2012} { Sapientia, Erdélyi Magyar Tudományegyetem }

Bevezetés a programozásba II. 5. Előadás: Másoló konstruktor, túlterhelés, operátorok

2019, Funkcionális programozás. 5. el adás. MÁRTON Gyöngyvér

2018, Funkcionális programozás

OOP #14 (referencia-elv)

A C# programozási nyelv alapjai

Már megismert fogalmak áttekintése

Tisztán funkcionális adatszerkezetek (folytatás)

Tisztán funkcionális adatszerkezetek

Alapok. tisztán funkcionális nyelv, minden függvény (a konstansok is) nincsenek hagyományos változók, az első értékadás után nem módosíthatók

Programozási nyelvek (ADA)

S0-02 Típusmodellek (Programozás elmélet)

Programozás burritokkal

C++ referencia. Izsó Tamás február 17. A C++ nyelvben nagyon sok félreértés van a referenciával kapcsolatban. A Legyakoribb hibák:

2018, Funkcionális programozás

Operációs rendszerek. 9. gyakorlat. Reguláris kifejezések - alapok, BASH UNIVERSITAS SCIENTIARUM SZEGEDIENSIS UNIVERSITY OF SZEGED

Java programozási nyelv 5. rész Osztályok III.

Web-technológia PHP-vel

Programozási nyelvek Java

Oktatási segédlet 2014

1.1. A forrásprogramok felépítése Nevek és kulcsszavak Alapvető típusok. C programozás 3

Programozás alapjai. 5. előadás

1. Template (sablon) 1.1. Függvénysablon Függvénysablon példányosítás Osztálysablon

Informatika terméktervezőknek

Bevezetés a C++ programozási nyelvbe

Programozás I. 3. gyakorlat. Szegedi Tudományegyetem Természettudományi és Informatikai Kar

Programfejlesztési Modellek

Programozási nyelvek Java

2016, Funkcionális programozás

Segédanyagok. Formális nyelvek a gyakorlatban. Szintaktikai helyesség. Fordítóprogramok. Formális nyelvek, 1. gyakorlat

A függvény kód szekvenciáját kapcsos zárójelek közt definiáljuk, a { } -ek közti részt a Bash héj kód blokknak (code block) nevezi.

Interfészek. PPT 2007/2008 tavasz.

ALAPFOGALMAK 1. A reláció az program programfüggvénye, ha. Azt mondjuk, hogy az feladat szigorúbb, mint az feladat, ha

Bánsághi Anna 2014 Bánsághi Anna 1 of 33

OBJEKTUM ORIENTÁLT PROGRAMOZÁS JAVA NYELVEN. vizsgatételek

A Feldspar fordító, illetve Feldspar programok tesztelése

Logika es sz am ıt aselm elet I. r esz Logika 1/36

C++ programozási nyelv

1. Egyszerű (primitív) típusok. 2. Referencia típusok

Bevezetés a programozásba Előadás: Objektumszintű és osztályszintű elemek, hibakezelés

A fordítóprogramok szerkezete. Kódoptimalizálás. A kódoptimalizálás célja. A szintézis menete valójában. Kódoptimalizálási lépések osztályozása

C programozási nyelv Pointerek, tömbök, pointer aritmetika

1. Bevezetés A C++ nem objektumorientált újdonságai 3

Algoritmizálás + kódolás C++ nyelven és Pascalban

KOMPUTER-ALGEBRA RENDSZEREK VERIFIKÁCIÓJA

Rekurzió. Dr. Iványi Péter

Cekla. Készítette Doxygen Tue Sep :13:44

Programozási nyelvek a közoktatásban alapfogalmak II. előadás

Aritmetikai kifejezések lengyelformára hozása

A C programozási nyelv I. Bevezetés

AWK programozás, minták, vezérlési szerkezetek

Algoritmizálás és adatmodellezés tanítása 1. előadás

Karakterkészlet. A kis- és nagybetűk nem különböznek, a sztringliterálok belsejét leszámítva!

Mindent olyan egyszerűvé kell tenni, amennyire csak lehet, de nem egyszerűbbé.

Alkalmazott modul: Programozás 4. előadás. Procedurális programozás: iteratív és rekurzív alprogramok. Alprogramok. Alprogramok.

Tömbök kezelése. Példa: Vonalkód ellenőrzőjegyének kiszámítása

Programozási nyelvek Java

Java programozási nyelv

Amortizációs költségelemzés

A C programozási nyelv I. Bevezetés

Programozás II. 2. gyakorlat Áttérés C-ről C++-ra

Halmazelmélet. 1. előadás. Farkas István. DE ATC Gazdaságelemzési és Statisztikai Tanszék. Halmazelmélet p. 1/1

OOP: Java 8.Gy: Abstract osztályok, interfészek

Imperatív és procedurális programozás a Javában

KOVÁCS BÉLA, MATEMATIKA I.

Programozás BMEKOKAA146. Dr. Bécsi Tamás 2. előadás

Mindent olyan egyszerűvé kell tenni, amennyire csak lehet, de nem egyszerűbbé. (Albert Einstein) Halmazok 1

Pénzügyi algoritmusok

AWK programozás Bevezetés

Operációs rendszerek. 9. gyakorlat. BASH recap, reguláris kifejezések UNIVERSITAS SCIENTIARUM SZEGEDIENSIS UNIVERSITY OF SZEGED

Bevezetés, a C++ osztályok. Pere László

Adatszerkezetek Adatszerkezet fogalma. Az értékhalmaz struktúrája

Bevezetés a programozásba

Formális szemantika. Kifejezések szemantikája. Horpácsi Dániel ELTE Informatikai Kar

Bevezetés a Programozásba II 2. előadás. Adattípusok megvalósítása egységbe zárással. Adattípusok megvalósítása egységbe zárással

Funkcionális programozás

Programozás II. 3. gyakorlat Objektum Orientáltság C++-ban

Operációs rendszerek. 11. gyakorlat. AWK - szintaxis, vezérlési szerkezetek UNIVERSITAS SCIENTIARUM SZEGEDIENSIS UNIVERSITY OF SZEGED

JAVA PROGRAMOZÁS 2.ELŐADÁS

A félév során előkerülő témakörök

Átírás:

Doktori értekezés Diviánszky Péter 2012.

Referenciák tisztán funkcionális nyelvekben Diviánszky Péter ELTE IK Informatika Doktori Iskola Az informatika alapjai és módszertana oktatási program vezető: Dr. Benczúr András, egyetemi tanár, mat. tud. doktora Programozási Nyelvek és Fordítóprogramok Tanszék témavezető: Dr. Horváth Zoltán tanszékvezető egyetemi tanár The research was supported by the Hungarian National Science Research Grant (OTKA), Grant Nr. T037742, by GVOP-3.2.2.-2004-07-0005/3.0 ELTE IKKK and OMAA-ÖAU 66öu2, by the European Union and co-financed by the European Social Fund (grant agreement no. TAMOP 4.2.1./B-09/1/KMR-2010-0003)

Papp Zoltán tanár úr és a bátyám révén kerültem kapcsolatba a tisztán funkcionális programozással, ami rögtön megragadott a matematikával való szoros kapcsolata miatt. Ezután Nijmegenben az Ivicsics Mátyással, Várnagy Zoltánnal és Rinus Plasmeijerrel való találkozás meghatározó volt abban, hogy később elkezdjem a doktori programot. Budapesten a tanszéki értekezleteken többek között Kozsik Tamás, Csörnyei Zoltán, Horváth Zoltán, Porkoláb Zoltán, Zsók Viktória, Lövei László és Tejfel Máté tudományos vitáit élvezhettem, majd Hegedűs Hajnalkával dolgozhattam együtt a Clean és Haskell fordítók közti kapcsolat megteremtésén. Csörnyei Zoltán a TDK témavezetésével is hozzásegített a doktori iskolában való részvételhez. A doktori program elkezdése után Szabóné Nacsa Rozáliával és Horváth Zoltánnal dolgozhattam együtt a funkcionális programok átfogalmazásán és több tudományos konferencián is részt vehettem velük. Eközben Erdélyi Gáspárral lelkesen kezdtük kutatni a referenciák szerinti egyenlőségvizsgálatot és a gráfok destruktív módosítását. Nijmegenben újra Rinus Plasmeijer segített a témavezetésével, John van Groningen pedig ABC kódot magyarázott. A bemutatott doktori értekezés témája innen ered. A munkámban közös beszélgetésekkel is segítettek többek között munkatársaim, Mészáros Mónika, László Ildikó, Kitlei Róbert, Dévai Gergely, Góbi Attila, és barátaim, Barczy Mátyás, Vértesi Tamás és Páli Gábor. A diákjaim és az F#, majd az MCore projekt tagjai gondoskodtak arról, hogy eközben véletlenül se unatkozzak. A munkámban többek között a tanszéki adminisztrátorok, a testvéreim, a szüleim, a rokonaim és édesanyám osztálytársai is támogattak, a témavezetőm hatékonyan rávett az egyes cikkek megírására, a nagymamám pedig rávett a doktori értekezés befejezésére. Mindezeket nagyon köszönöm!

Tartalomjegyzék 1. Tézisek 9 1.1. Kapcsolódó publikációk.................... 11 2. Bevezetés 13 2.1. Hivatkozások behelyettesíthetősége.............. 13 2.1.1. Kapcsolódó fogalmak................. 14 2.2. Kihívások a hivatkozások behelyettesíthetősége esetén... 15 2.2.1. Referenciák megvalósítása............... 16 2.3. Megoldások összefoglalása................... 17 3. Bevezetés a tisztán funkcionális programozásba 21 3.1. A Haskell modulrendszere................... 21 3.2. Lexikális elemek........................ 22 3.3. Kifejezések........................... 23 3.3.1. Függvényalkalmazás.................. 23 3.3.2. Operátorok alkalmazása................ 24 3.3.3. N-esek.......................... 24 3.3.4. Listák.......................... 24 3.4. Függvénydefiníciók....................... 26 3.4.1. Egyszerű függvény- és konstansdefiníciók...... 26 3.4.2. Mintaillesztés...................... 26 3.4.3. Esetszétválasztás.................... 29 3.4.4. Lokális definíciók.................... 30 3.5. Magasabbrendű függvények.................. 30 3.6. Szigorú statikus típusozás................... 31 3.7. Típusdefiníciók......................... 34 3.7.1. Típus szinonima definíciók............... 34 3

3.7.2. Algebrai adattípusok.................. 34 3.7.3. Absztrakt típusok................... 36 3.8. Típusosztályok......................... 36 3.8.1. Típusosztályok definiálása............... 37 3.9. Dinamikus típusozás...................... 38 3.10. Akciók.............................. 38 3.10.1. Az akciók típusa.................... 39 3.10.2. Elemi akciók, akciók összekötése........... 40 3.10.3. A do kifejezés...................... 41 3.11. Monádok............................ 42 3.11.1. Példa monádokra.................... 43 3.12. Funktorok............................ 43 3.13. Kiértékelési stratégiák..................... 44 3.13.1. A kiértékelési sorrend módosítása........... 45 3.13.2. A érték........................ 47 3.14. Haskell nyelvi kiterjesztések.................. 47 4. Felhasznált módszerek 49 4.1. Veszélyes függvények...................... 49 4.2. Magasabb rangú típusok.................... 49 4.3. Egzisztenciális típusozás.................... 50 4.4. Fantom típusváltozók..................... 51 4.5. Számítások típusokon..................... 52 4.5.1. Adatok típusokkal való reprezentálása........ 52 4.5.2. Típusfüggvények.................... 53 4.6. Egyediségi típusozás...................... 54 4.6.1. Bevezetés az egyediségi típusozásba......... 54 4.6.2. Egyediség ellenőrzés futási időben.......... 55 4

5. Kapcsolódó munkák 57 5.1. Véges leképezések........................ 57 5.1.1. A véges leképezés műveleteinek erőforrásigénye... 58 5.2. Monadikus referenciák..................... 58 5.2.1. IO-referenciák...................... 58 5.2.2. ST-referenciák..................... 60 5.2.3. Monád-független interfészek.............. 62 5.3. Heapek Cleanben........................ 64 5.3.1. Referencia egyszerűsített olvasása........... 65 5.3.2. Problémák....................... 65 5.4. Mellékhatás-típusozás..................... 65 6. Referenciák kiváltása véges leképezésekkel 69 6.1. Példa a véges leképezések használatára............ 69 6.2. Módosított véges leképezések................. 71 6.2.1. Típusváltozóval címkézett azonosítók......... 72 6.3. Példák a használatra...................... 74 6.3.1. Véges halmazok..................... 74 6.3.2. Erősen összefüggő gráfkomponensek kiszámolása.. 74 6.3.3. Reláció inverzének kiszámolása............ 76 6.3.4. Erősen összefüggő gráfkomponensek kiszámolása.. 77 6.3.5. A sorozat adatszerkezet................ 77 6.4. A módosított véges leképezések implementálása....... 80 6.4.1. A foltozási technika alapjai.............. 80 6.4.2. A végleges adatszerkezetek.............. 83 6.4.3. Az azonosítók műveletei................ 84 6.4.4. Kiegészítések az implementációhoz.......... 84 6.5. Összefoglalás.......................... 85 5

7. Referenciák kiváltása heapekkel 87 7.1. Bevezetés............................ 87 7.2. Általános interfész....................... 88 7.2.1. Referencia olvasás és írás............... 88 7.2.2. Új referencia készítése................. 89 7.2.3. Egyedi állapot megkettőzése.............. 90 7.2.4. Mag létrehozása.................... 90 7.3. Heap magok........................... 90 7.3.1. Hatékony implementáció................ 91 7.3.2. Példa (első változat).................. 92 7.4. Referencia implementáció................... 94 7.4.1. A hatékony és a referencia implementáció kapcsolata 95 7.5. Homogén heapek........................ 96 7.5.1. Referencia implementáció............... 97 7.6. Különálló magok........................ 98 7.6.1. Hatékony implementáció................ 98 7.6.2. Referencia implementáció............... 99 7.7. Törölhető referenciák...................... 100 7.7.1. Referencia implementáció............... 100 7.7.2. Általános függvények.................. 101 7.8. Osztott referenciák....................... 102 7.8.1. Hatékony implementáció................ 102 7.8.2. Referencia implementáció............... 104 7.8.3. Példa (második változat)............... 105 7.9. Heapek uniója.......................... 106 7.9.1. Címke altípusozás................... 107 7.9.2. Az unió típusosztály.................. 109 7.9.3. Példa (harmadik változat)............... 109 6

7.10. Referenciák egyenlőségvizsgálata............... 111 7.11. A modellek összehasonlítása.................. 112 7.12. Alkalmazások.......................... 113 7.12.1. Konstans memóriát használó szemétgyűjtés..... 114 7.12.2. Erősen összefüggő gráfkomponensek kiszámolása.. 115 7.12.3. Diszjunkt halmazok.................. 118 7.12.4. Típusegyenlet-rendszer megoldása.......... 120 7.13. Összefoglalás.......................... 123 8. Algebrai adatban szereplő referenciák azonosságvizsgálata125 8.1. Bevezetés............................ 125 8.1.1. Az alapötlet....................... 126 8.1.2. Áttekintés........................ 127 8.2. Az alapnyelv.......................... 127 8.3. Az új nyelvi elemek definíciója................ 129 8.3.1. Szintaxis........................ 129 8.3.2. Szemantika és típusozás................ 130 8.3.3. A kibővített nyelv bővítése újabb nyelvi elemekkel. 132 8.4. Egy teljes példa......................... 133 8.5. Fordítás gráfátíró rendszerekkel................ 134 8.5.1. Gráfátíró rendszerek.................. 134 8.5.2. A gráfátíró rendszer kibővítése............ 134 8.6. A kibővített nyelv tulajdonságai............... 137 8.7. A new terjedési szabály enyhítése.............. 139 8.8. Prototípus implementáció Cleanben............. 139 8.8.1. Az ABC nyelv..................... 139 8.9. Kapcsolódó munkák...................... 141 8.9.1. A ν-kalkulus...................... 141 8.10. Összefoglalás.......................... 142 7

9. Összegzés 143 10.Summary 144 11.Függelék: Gráfátíró rendszerek 149 8

1. Tézisek A doktori értekezésemben megmutatom, hogy a referenciák a monadikus modell mellett milyen más konstrukciókkal válthatók ki tisztán funkcionális nyelvekben. Első tézis Referenciák hatékonyan kiválthatók véges leképezésekkel Definiáltam egy módosított véges leképezés adatszerkezetet, amely hatékonysága miatt alkalmas referenciák kiváltására. 6. fejezet, [8], [9] cikkek, [10] kapcsolódó cikk. A véges leképezés adatszerkezet egy olyan asszociációs listának felel meg, amelyben a kulcs érték párok sorrendje nem definiált. A referenciák kiválthatók véges leképezésekkel úgy, hogy a referenciák helyett kulcsokat használunk és a referenciák által hivatkozott értékeket egy, a kulcsokat tartalmazó véges leképezésben tartjuk. Amennyiben referenciák kiváltására használjuk a véges leképezéseket, a következő feltételek általában teljesíthetőek: Bármely kulcs értékének írása után a kulcshoz tartozó korábbi értéket nem írjuk vagy olvassuk. Ez a feltétel teljesül referenciák esetén mivel ott nincs lehetőség a referencia írása előtti környezetre hivatkozni. A program futása során használt véges leképezések számára előre rögzített felső korlát van. Referenciák esetén ahol csak egyetlen, globális környezet áll rendelkezésre, aminek a feldarabolásával kapjuk a véges leképezéseket, amiknek a száma többnyire fordítási időben rögzített. A módosított véges leképezések elemi műveletei, úgymint az írás, az olvasás, a kulcsok készítése és összehasonlítása konstans erőforrás igényűek, ha a fenti két természetes feltétel teljesül. Ez a tény alátámasztja hogy a módosított véges leképezések alkalmasak referenciák kiváltására. Az elemi műveletek konstans erőforrásigényéből következik, hogy a program futása során használt kulcsok száma nem befolyásolja a műveletek 9

erőforrás igényét. Ez a tulajdonság eltér a szakirodalomban ismert véges leképezés adatszerkezetekétől, amikben az elemi műveletek hatékonysága egyenesen arányos a kulcsok számának a logaritmusával. Második tézis Általános interfész adható heapeknek Definiáltam egy általános heap interfészt mellyel lehetséges referenciák távoli létrehozása, heterogén és homogén heapek használata, osztott referenciák használata és a heap unió művelete, és ezen interfész hat különböző lehetséges implementációjának szemantikai vizsgálatát is elvégeztem. 7. fejezet, [12] cikk. Heapnek nevezzük a memória absztrakt leírásában azt a részt, amely memóriahely érték párok halmazaként képzelhető el. A heap tekinthető egy nem perzisztens véges leképezésnek is. Heapekkel kiváltható a referenciák használata tisztán funkcionális nyelvekben, ha a nyelv támogatja az egyediségi típusozást. Definiáltam egy általános heap interfészt. Az interfészben lehetővé tettem referenciák távoli létrehozását, heterogén és homogén heapek használatát, és referenciák megosztott használatát. Definiáltam a heap unió műveletet is. Megadtam 6 lehetséges heap modellt az általam definiált heap interfészhez, és ezeket össze is hasonlítottam. Megmutattam, hogy a Clean nyelvben használt heapek elláthatók az általam definiált interfésszel, és ezzel javítható a korábbi Clean heap interfésznek egy jelentős hibája. Harmadik tézis Biztonságossá tehető a referencia szerinti azonosságvizsgálat Kidolgoztam az algebrai adattípusok referencia szerinti azonosságvizsgálatát. 8. fejezet, [7] cikk, [5], [6] kapcsolódó cikkek. 10

Funkcionális nyelvekben az algebrai adattípusok implementációja többnyire referenciákkal történik: minden konstruktorból referenciák mutatnak a konstruktor paramétereihez. Ezek a referenciák mintaillesztéssel olvashatók, a referenciák írása és azonosságvizsgálata viszont nem megengedett, mivel elrontaná a hivatkozások behelyettesíthetőségét. Kibővítettem egy tisztán funkcionális nyelvet referencia szerinti egyenlőségvizsgálattal úgy, hogy egyszerű feltétel adható a hivatkozások behelyettesíthetőségére és teljesül a függvénydefiníciók behelyettesíthetősége, szemben a ν-kalkulus[29] megoldásával. Ezeket az állításokat bizonyítottam is. 1.1. Kapcsolódó publikációk Folyóirat cikkek [11], [12]. Referált cikkek [7], [8]. Absztraktok [5], [6], [9], [10]. 11

12

2. Bevezetés 2.1. Hivatkozások behelyettesíthetősége A funkcionális nyelvek sikerében jelentős szerepet játszik a hivatkozások behelyettesíthetősége. A hivatkozások behelyettesíthetőségének definiálásához először egy-egy példával megnézzük a változó helyettesítés és a környezet fogalmát. Példa változó helyettesítésre: (1 / x + 3 x)[x := 6 + 5] = 1 / (6 + 5) + 3 (6 + 5). A környezet nem más mint definíciók halmaza, például {x = 4 5; y = 3 + x}. A hivatkozások behelyettesíthetősége (angolul referential transparency) egy adott környezet, egy kifejezés és egy változó esetén azt jelenti, hogy a kifejezésben a változó kicserélhető a definíciójára anélkül, hogy a kifejezés jelentése megváltozna: e e[v := a] ha {... ; v = a;... } Példa: Legyen a környezet {x = 1 + 1}, legyen a kifejezés x + x és legyen a változó x. Az adott környezetben az x + x kifejezés és az (1 + 1) + (1 + 1) kifejezés jelentése közt nincs különbség. Ellenpélda: Legyen a környezet {x = random (0, 1)}, ahol random (a, b) egy a és b közötti véletlen szám egyenletes eloszlással. Legyen a kifejezés x + x és legyen a változó x. Ekkor az x + x kifejezés jelentése nem azonos a random (0, 1) + random (0, 1) kifejezés jelentésével 1, azaz ebben az összefüggésben nem teljesül a hivatkozások behelyettesíthetősége. Úgy is tudjuk cáfolni a hivatkozások behelyettesíthetőségét, hogy találunk egy e kifejezést amire az e e egyenlőségvizsgálat értéke hamis lesz. Mivel random (0, 1) random (0, 1) általában hamis, így ezért sem teljesül a hivatkozások behelyettesíthetősége. A doktori értekezésben tisztán funkcionális programozási nyelvnek nevezem az olyan programozási nyelvet, amelyben teljesül a hivatkozások behelyettesíthetősége, vagy egyszerű feltétel adható a hivatkozások behelyet- 1 A 2 random (0, 1) egy 0 és 2 közötti véletlen szám egyenletes eloszlással, ezzel szemben a random (0, 1) + random (0, 1) eloszlása nem egyenletes, az 1 körüli számok a legvalószínűbbek. 13

tesíthetőségére. A definíció alapján tisztán funkcionális nyelvek például a Haskell, a Clean 2 és a Miranda. A hivatkozások behelyettesíthetőségének definíciójában a kifejezés jelentése fogalom külön magyarázatra szorul. Általában egy kifejezés jelentése alatt a kifejezés funkcionális viselkedését értjük. A funkcionális viselkedésbe nem tartozik bele a futási idő vagy a memóriahasználat, de beletartozik például az, hogy a program adott bemenetre milyen kimenetet ad. A hivatkozások behelyettesíthetősége tehát nem azt jelenti, hogy a behelyettesítés során változatlan marad a program minden tulajdonsága. Ezt a tényt használjuk ki a tisztán funkcionális programok optimalizálásakor. Az hivatkozások behelyettesítésével kapott optimalizációs lépések csak nagyon korlátozottan alkalmazhatók olyan nyelvek esetén, ahol a hivatkozások behelyettesíthetősége komplex feltételhez kötött (például imperatív nyelvekben). A hivatkozások behelyettesíthetőségének előnyei: egyszerűbb programhelyesség bizonyítás, például egyenlőségi érvelés használata, kódoptimalizálási lehetőségek, parciális kiértékelés fordítás közben, nagyobb szabadság a kiértékelési stratégia megválasztásában, párhuzamos kiértékelés lehetősége. 2.1.1. Kapcsolódó fogalmak Van több szorosan kapcsolódó fogalom, amik általában a hivatkozások behelyettesíthetőségével egy időben teljesülnek. Ezekre a fogalmakra nem lesz szükségünk, de megemlítjük őket, hogy érzékeltessük a hivatkozási helyfüggetlenség fogalom fontosságát: Hivatkozási helyfüggetlenség (definiteness) Egy változó a hatókörén belül mindenhol ugyanazt a dolgot jelenti. Például a 3 x 2 + 2 x + 17 kifejezésben az x mindkét helyen ugyanazt az értéket jelöli. 2 A Cleanben a hivatkozások behelyettesíthetőségét az egyszeres hivatkozásra vonatkozó típusozási szabályok korlátozzák. 14

Függvénydefiníciók behelyettesíthetősége (unfoldability, β-reduction) Egy függvényalkalmazás kicserélhető a függvény törzsére. Például, ha f (x) = x + x, akkor a 2 f (1 + 1) kifejezés helyett használhatjuk a 2 ((1 + 1) + (1 + 1)) kifejezést. Egyenlők behelyettesíthetősége (Leibniz s law, substitutivity of identity) Ha a=b, akkor ez a két kifejezés tetszőleges környezetben felcserélhető. Perzisztencia (persistence) A hivatkozás értéke nem változik meg műveletvégzéskor. (Perzisztencia alatt az értekezésben mindig teljes perzisztenciát értünk.) Kiterjeszthetőség (extensionality) Egy kifejezésről mindent elmond az értéke. Determinisztikusság (determinacy) Egy kifejezésnek mindig ugyanaz az értéke. Mellékhatásmentesség (lack of side effects) Kifejezés nem jár mellékhatással. kiértékelése Ezeknek a fogalmaknak az egymáshoz való viszonyát például a [28] cikk tárgyalja. 2.2. Kihívások a hivatkozások behelyettesíthetősége esetén A hivatkozások behelyettesíthetősége számos előnnyel jár, de következő feladatok kihívást jelentenek a hivatkozások behelyettesíthetőségének betartásakor: nem determinisztikus programok írása, véletlen számok használata, értékadás, változók és referenciák hatékony megvalósítása, destruktív módosítás, például tömbök hatékony használata, programszálak közti kommunikáció, I/O műveletek: adatbázis kezelés, fájlkezelés, hálózati kommunikáció, kommunikáció a felhasználóval. 15

Ezekre a kihívásokra több megoldás is létezik; a három legismertebb tisztán funkcionális nyelv, a Miranda, a Clean és a Haskell nyelv három alapvetően különböző megoldást talált. A Haskell megoldását a 3.10 fejezetben, a Clean megoldását a 4.6 fejezetben tárgyaljuk. 2.2.1. Referenciák megvalósítása A doktori értekezésben a referenciák kiváltásának kérdésével foglalkozom tisztán funkcionális nyelvekben, ami viszont kapcsolatba hozható az előző felsorolás többi kihívásával is. A következő kód megmutatja, hogy miért problematikus a hivatkozások behelyettesíthetősége referenciák használta esetén. let r = newref 4 x = readref r in writeref r 5 return (x + readref r) Az lenne a legkevésbé meglepő ha a 9 értéket kapnánk eredményül, viszont az x hivatkozás behelyettesítése után viszont az lenne a legkevésbé meglepő, ha a 10 értéket kapnánk eredményül: let r = newref 4 in writeref r 5 return (readref r + readref r) A két kódnak azonosnak kellene lennie ha teljesül a hivatkozások behelyettesíthetősége, viszont a két kód azonossága nehezen teljesíthető, mivel különböző végeredményt várunk el tőlük. Nem állítom, hogy a szemantika módosításával nem lehetséges összeegyeztetni a referenciák használatát a hivatkozások behelyettesíthetőségével, viszont a doktori értekezésben nem a programozási nyelvek szemantikájának módosításával kívánok eredményt elérni, hanem az ismert szemantikák mellett újabb programnyelvi konstrukciók bevezetésével. 16

2.3. Megoldások összefoglalása Tisztán funkcionális nyelvekben a következő lehetőségeket azonosítottam amikkel referenciák kiválthatók: 1. Tisztán funkcionális adatszerkezetek használata Rövid leírás A véges leképezés adatszerkezettel jól modellezhetők a referenciák. Előnyök A programozónak nagy szabadsága van, mivel a tisztán funkcionális adatszerkezetek perzisztensek. Lehetőség van például az összes referencia értékének egy adott időpontban való megjegyzésére és későbbi visszaállítására. Hátrányok A perzisztencia lehetetlenné teszi az adatszerkezetek destruktív módosítását, ami kizár sok hatékony implementációs lehetőséget. Például a (perzisztens) véges leképezés elemi műveleteinek hatékonysága csökken az elemszám növekedésével. Felhasználás az értekezésben Definiáltam egy módosított véges leképezés adatszerkezet, ami hatékony bizonyos természetes feltételek mellett, amik teljesülnek ha referenciák modellezésére használjuk a leképezéseket. Erről szól az első tézis (6). 2. A műveletek mellékhatásának elkülönítése Ha elkülönítjük a mellékhatásos kifejezéseket a mellékhatásmentesektől, akkor biztonságosan ki tudjuk használni a hivatkozások behelyettesítésének az előnyeit legalább a mellékhatásmentes kifejezések esetén. A mellékhatások elkülönítésének módjai: (a) programhelyesség bizonyítással Rövid leírás A programhelyesség bizonyítás során minden információ rendelkezésre áll ahhoz, hogy a kifejezések mellékhatásait vizsgáljuk. A referencia műveletek mellékhatásának elkülönítésére például a szétválasztási-logika [26] (angolul separation logic) használható. Előnyök A programkód egyszerű marad, mert mellékhatásokra vonatkozó információ a programra vonatkozó állításokban és bizonyításokban van. 17

Hátrányok A hivatkozások behelyettesíthetősége komplex feltételhez kötött, ami többnyire a programkódtól elkülönítve jelenik meg, ezért a mellékhatások elkülönítése programhelyesség bizonyítással nem tekinthető a tisztán funkcionális programozás eszközének. A helyességbizonyítás során kapott információ nehezen vehető igénybe a program fordításakor, például optimalizáció céljából. Ha a szétválasztási logikát a típusrendszerrel fejezzük ki, akkor a mellékhatás-típusozással kapcsolatos hátrányok jelentkeznek. Felhasználás az értekezésben Ezzel a módszerrel nem foglalkozok az értekezésben, mert a mellékhatások elkülönítését programhelyesség bizonyítással nem tekintem a tisztán funkcionális programozás eszközének. (b) mellékhatás-típusozással Rövid leírás A mellékhatás-típusozás (angolul effect-typing, 5.4. fejezet) a kifejezések mellékhatásait a típusrendszer segítségével követi. Előnyök A programkód egyszerű marad, mert mellékhatásokra vonatkozó információ a típusban van, ami esetleg automatikusan ki is következtethető. Hátrányok A hivatkozások behelyettesíthetősége komplex feltételhez kötött, ami csökkenti a hivatkozások behelyettesíthetőségéből származó előnyöket, például nehezebb egyenlőségi érvelést végezni. A mellékhatás-típusozási rendszerek meglehetősen komplexek, a nem mohó kiértékelési stratégiák különösen nehezen kezelhetőek. Felhasználás az értekezésben Bizonyos mellékhatások esetében egyszerű feltétel adható a hivatkozások behelyettesítéséhez, például referenciák egyenlőségvizsgálata esetén. Ekkor a mellékhatás-típusozás vagy hasonló módszer egyszerűen használható marad; ezt mutatom be a harmadik tézisben (8). (c) egyediségi típusozással Rövid leírás Az egyediségi típusozás (4.6. fejezet) a mellékhatás-típusozás egy egyszerűsített változatának tekinthető, ahol az egyetlen lehetséges mellékhatás az, 18

hogy egy értéket egy művelet során már felhasználtunk, és ezzel a mellékhatással fejezünk ki minden más mellékhatást. Előnyök A kód olvasásakor például egyenlőségi érvelés esetén a hivatkozások behelyettesíthetőse feltétel nélkül alkalmazható. A programkód egyszerű marad, mert a mellékhatásokra vonatkozó információ a típusban van, ami automatikusan kikövetkeztethető. Hátrányok A hivatkozások behelyettesíthetősége komplex feltételhez kötött programírás például programtranszformáció esetén. A típusrendszert alkalmassá kell tenni az egyediségi típusozásra, ami típusattribútumokkal történhet. Felhasználás az értekezésben A második tézisben (7) az egyediségi típusozás egy egyszerűsített változatát használom a referencia műveletek mellékhatásának elkülönítésére. (d) monádokkal Rövid leírás A monádok (3.11. fejezet) a mellékhatást a programkódban explicit módon jelzik, és megadják, hogy a részkifejezések mellékhatásából hogyan számítható ki az egész kifejezés mellékhatása. Előnyök A hivatkozások behelyettesíthetősége feltétel nélkül alkalmazható. A monádok nem igényelnek speciális típusozást, a monadikus műveleteknek típusa típuskonstruktorokkal és univerzális kvantálással leírható. Hátrányok A mellékhatásokat a programkódban is jelezni kell, és előfordul hogy ugyanannak az algoritmusnak meg kell adni egy mellékhatásokat kezelő és nem kezelő változatát is. A hivatkozások behelyettesíthetősége teljesül ugyan, de a monádok használata indokolatlanul rögzítheti a kiértékelési sorrendet, így a helyességbizonyítás lépései kötöttebbek lesznek, valamint fordítóprogramnak kevesebb tere marad az optimalizációra, ami érdekes módon pont a hivatkozások be nem helyettesíthetőségének egy tünete. Egymástól független mellékhatások kezelése monádokkal nehézkes. Felhasználás az értekezésben A referenciák monádokkal 19

való megvalósítása már kidolgozott, amit az 5.2 fejezetben mutatok be. Összegzésképpen elmondható, hogy mind a négy módszernek 3 megvan az előnye és a hátránya is, és az alkalmazáshoz igazodva érdemes a leginkább megfelelő módszert kiválasztani. 3 A mellékhatások elkülönítését programhelyesség bizonyítással nem tekinthető a tisztán funkcionális programozás eszközének, így négy módszer marad. 20

3. Bevezetés a tisztán funkcionális programozásba A doktori értekezésben a Haskell nyelv szintaxisát és szemantikáját használom; a későbbi fejezetekben definiált interfészeket és azok implementációját, valamint a felhasználási példákat is Haskell nyelven adom meg. 3.1. A Haskell modulrendszere A Haskell nyelv definíciós és fordítási egysége a modul. Minden Haskell modul egy szövegfájl, ami az alábbi részekből áll, kötött sorrendben. fejléc: modul modulnév where A modulnév hierarchikus felépítésű, mint például System.IO.Error. A modulnévnek összhangban kell lenni a modulnak a fájlrendszerben való elhelyezkedésével, például a Data.Map modulnak a Data könyvtár Map.hs szövegfájljában kell elhelyezkednie. export lista A modulok különböző azonosítókat exportálhatnak, például függvény- és típusazonosítókat. A nem exportált azonosítók csak a modulon belül láthatóak. import lista Itt soroljuk fel, hogy a modul milyen egyéb modulokat importál. Körkörös importálás lehetséges, de nem ajánlott a használata. Az importált modulok exportált azonosítói láthatóak lesznek a modulon belül, és akár tovább is exportálhatóak. deklarációk A deklarációk sorrendje a Haskell nyelvben nem számít. Az deklarációk típusai (ezeket a következő alfejezetekben mutatjuk be részletesen): Konstansdefiníció: pi = 2 acos 0 Függvénydefiníció: f x = x Speciális függvénydefiníciók: 21

Operátor definíció: a b = (a > b) Az operátorok kétparaméteres függvények speciális szintaxissal. A konstansok nulla paraméterű függvényeknek tekinthetők. Típusdeklaráció: f :: Int Int Típusdefiníció Típus szinonima definíció: type String = [ Char] Algebrai adattípus definíció: data Bool = True False Típusosztály definíció: class Num a where... Típusosztály példány definíció: instance Num Int where... A modul fejléc, az importálás és az exportálás szintaxisával és pontos szemantikájával nem foglalkozunk, mivel ezek nem szükségesek a doktori értekezés megértéséhez. A modulok programokat és programkönyvtárakat alkotnak, amik csomagokban terjeszthetők. Minden programban szerepelnie kell egy legfelső szintű modulnak. A legfelső szintű modul áttételesen importálja az összes többi modult, és exportálja a main azonosítót, ami lefordított program belépési pontja lesz. Programkönyvtárakra nincs semmilyen megkötés, ezek egyszerűen modulok halmazai. A csomagok közt is van egy függőségi reláció, ami nem lehet körkörös. A legfontosabb csomag a base, ami tartalmazza a Prelude nevű modult is. A Prelude modul sok alapvető definíciót tartalmaz és automatikusan importált minden modulba, hacsak külön nem kérjük ennek az ellenkezőjét. 3.2. Lexikális elemek A következő lexikális elemeket fogjuk használni: Egysoros megjegyzés: -- megjegyzés Az egysoros megjegyzés a sor végéig tart. Literál 22

Decimális egész szám literál: 123,... Decimális tört szám literál: 1.23,... Unikód karakter literál: x, é,?,... Szöveg (sztring) literál: "szöveg",... Speciális karakter (){}[] ";, Kulcsszó data, type, class, instance, infix, infixl, infixr, where, case, of, do, let, in, =,, ::,,, Azonosító Kis kezdőbetűs alfanumerikus azonosító: length, f, f, x1,... Ezeket függvény- és változónévként használjuk. Nagy kezdőbetűs alfanumerikus azonosító: Bool, True,... Ezeket típuskonstruktor- és konstruktornévként használjuk. Grafikus karakterekből álló azonosító: +,,,,,... Ezeket operátornévként használjuk. 3.3. Kifejezések A szám, karakter és szöveg literálokon kívül a következő módokon készíthetünk kifejezéseket. 3.3.1. Függvényalkalmazás A függvényalkalmazás jele a szóköz: sin 0.5 A függvényalkalmazás balra köt, azaz (min 5 2) esetén a min függvénynek átadjuk az 5 paramétert, majd az eredmény függvénynek (amely minden számnak veszi az 5-tel való minimumát) átadjuk a 2 paramétert. A többparaméteres függvények szokásos reprezentációja a Haskellben egyparaméteres függvényekkel történik, amit körrizésnek hívunk. Erre mindig van lehetőség, mert az ((A B) C) függvénytér izomorf az (A (B C)) függvénytérrel. A kiértékelési sorrend megváltoztatásához a szokásos módon zárójeleket használunk: abs (sin 2) Az (abs sin 2) jelentése ((abs sin) 2) ami típushibás kifejezés. 23

3.3.2. Operátorok alkalmazása A +,,,/, operátorok kötési erőssége a szokásos, így például (3 5 2 4) jelentése (3 ((5 2) 4)). A +,,, / operátorok balra kötnek, a hatványozás operátor pedig jobbra köt, azaz például (1 1 2 2 2) jelentése ((1 1) (2 (2 2))). A függvényalkalmazás erőssebb minden operátornál, így például (sin 1.1+ 1) jelentése ((sin 1.1) + 1). Operátorokat használhatunk prefix jelölésmóddal is, például ((+) 1 2) jelentése (1 + 2). Kétparaméteres függvényeket pedig használhatunk operátorként is, például (1 mod 2) jelentése (mod 1 2). Az úgynevezett szeletekkel az operátor egyik paraméterének elhagyásával képezhetünk függvényt. Például ( 2) a négyzetre emelés, (2 ) a 2 hatványára emelés függvénye. 3.3.3. N-esek Az n-esek segítségével különböző típusú kifejezéseket sorolhatunk fel egymás mellett (a típusozásról később lesz szó). Az n-esek alkalmasak például rekordok helyettesítésére, így például abban az esetben amikor egy függvény többféle végeredményt ad vissza nem szükséges egy külön adatszerkezetet definiálnunk a függvény végeredményének. Példák párokra: (1, c ), (3 + 2, True),... Példák hármasokra: (1, c, True),... 3.3.4. Listák A listák különböző számú, azonos típusú kifejezés felsorolására alkalmas (a típusozásról később lesz szó). Az üres lista jele: [ ]. Példák nem üres listákra: [1, 1 + 1, 3], [True], [ a, b ],... A szövegek (sztringek) karakter listák: "abc" ugyanaz, mint [ a, b, c ]. Az úgynevezett pont-pont kifejezések segítségével számtani sorozatokat rövidíthetünk: 24

[1.. 4] ugyanaz, mint [1, 2, 3, 4]. [1, 3.. 10] ugyanaz, mint [1, 3, 5, 7, 9]. [1..] az összes pozitív egész szám listája (a végtelen listákról később lesz szó). [1, 3..] az összes páratlan szám listája. A halmazkifejezések segítségével összetett listákat adhatunk meg tömören. Itt csak néhány példát mutatunk: A 2 hatványai növekvő sorrendben: [2 n n [1..]] A négyzetszámok növekvő sorrendben: [n 2 n [1..]] A 60 osztói: [n n [1.. 60], mod 60 n 0] A dominók reprezentálása párokkal: [(n, m) n [0.. 9], m [0.. 9], n m] vagy másképpen [(n, m) n [0.. 9], m [n.. 9]] A listák az üres listából és a (:) konstruktor alkalmazásából épülnek fel, ahol (:) egy elemet ad a lista elejéhez. Például [ 2, 3, 4] egy rövidített jelölés a 2 : 3 : 4 : [ ] kifejezésre. A legfontosabb függvények listákon: lista hossza: (length [3, 4]) értéke 2, listák összefűzése: [1, 2] + [3, 4] értéke [1, 2, 3, 4]. hajtogatás: foldr f e xs az xs listában kicseréli a (:) konstruktort f-re és a [ ] konstruktort e-re. Például foldr (+) 0 (1 : 2 : 3 : [ ]) = 1 + 2 + 3 + 0. Érdekesség, hogy a hajtogatással minden listán értelmezett függvény leírható, például xs + ys = foldr (:) ys xs. A balról hajtogatás, foldl a megfordított listán hajtogat. 25

3.4. Függvénydefiníciók 3.4.1. Egyszerű függvény- és konstansdefiníciók Egyszerű függvénydefiníció szintaxisa: függvénynév változónevek = kifejezés A kifejezés hivatkozhat a változókra. Például a következő függvény megállapítja hogy egy év szökőév-e (a logikai vagy operátor gyengébben köt, mint a logikai és operátor): isleapyear n = n mod 4 0 n mod 100 0 n mod 400 0 Másik példa: a következő többparaméteres függvény megállapítja, hogy három valós szám lehet-e valamely háromszög három oldalának oldalhossza: aretrianglesides x y z = x + y > z y + z > x z + x > y Konstans definiálásakor nem adunk meg egy változónevet sem: pi = 4 atan 1 Operátorok definiálásakor már az egyenlőségjel bal oldalán is infix jelölésmódot használunk: x z = (x y) Az operátorok kötési erősségét is megadhatjuk: infix 4 Ha infix helyett az infixl vagy infixr kulcsszavakat használjuk akkor az operátor balra illetve jobbra kötő lesz. 3.4.2. Mintaillesztés A mintaillesztés során a függvényt úgy definiáljuk, mintha látnánk, hogy a függvényt milyen paraméterrel hívták meg. Például a logikai tagadás definíciója Haskellben: :: Bool Bool True = False False = True 26

A függvény egyes definícióit függvényágaknak nevezzük, az előző függvénynek például két ága van. Fontos tudnivaló, hogy nem vizsgálhatunk tetszőleges kifejezést az egyenlőségjel bal oldalán. Például a következő definíciót nem fogadja el a Haskell: :: Bool Bool ( x) = x -- hibás True = False False = True Azokat a kifejezéseket, amik szerepelhetnek az egyenlőségjel bal oldalán a függvény neve mögött, mintának nevezzük. A minták nem részhalmazai a kifejezéseknek, van olyan minta is amit nem használhatunk kifejezésként. Kifejezések amelyek minták is: változók: x, xs, y, a,... A változó minta bármire illeszkedik, és az egyenlőségjel jobb oldalán a változóval tudunk hivatkozni az illesztett értékre. úgynevezett konstruktor alkalmazások: True, 0, (a, b),... Minták amelyek nem kifejezések: joker minta: A joker minta bármire illeszkedik. Az illesztett értékre nem tudunk hivatkozni. Akkor használunk joker mintát, ha az illesztett adat nem vesz részt a végeredmény kiszámolásában. összefogó minta: v@p ahol v változó, p minta. Az összefogó minta esetén amellett hogy mintaillesztést végzünk a p minta szerint, az illesztendő kifejezést kötjük is a v változóhoz. A joker alkalmazására példa a logikai ÉS definíciója: True x False = x = False Ha az függvényágak nem zárják ki egymást akkor lényeges a függvényágak sorrendje. Ha több függvényág is illeszkedik, akkor az első illeszkedő függvényág határozza meg a végeredményt. Például a következő definíció is helyes: 27

True x = x = False Konstruktor alkalmazásoknak számítanak a következő kifejezések: szám, karakter és szöveg literálok, a logikai értékek két konstruktora, a True és a False, párok mintái: (x,y) alakú kifejezések ahol x és y tetszőleges minta; hasonló a helyzet hármasok, négyesek stb. esetén, az üres lista [ ], véges hosszúságú listák, például három elem esetén [x,y,z] ahol x, y és z minták, elem hozzáadása a lista elejéhez (x:xs) ahol x és xs minták. Például a következő rekurzív függvény összegzi egy lista elemeit: sum [ ] = 0 sum (x : xs) = x + sum xs Gyakori példa a funkcionális gyorsrendezés is: qsort [ ] = [ ] qsort (x : xs) = qsort [y y xs, y < x] + [x] + qsort [y y xs, y x] Az összefogó mintára egy példa f xs@( H : _) = xs f = [ ] ahol f a H karakterrel kezdődő listákon identikus függvényként viselkedik, a többi listához pedig az üres listát rendeli. Esetszétválasztást végezhetünk kifejezéseken belül is a case konstrukcióval. Az előző f függvény például így is írható: f xs = case xs of ( H : _) xs; [ ] Megjegyezzük, hogy konstansdefiníció esetében a konstans neve helyett szerepelhet tetszőleges minta. Például (x : xs) = [1..] esetén x értéke 1 lesz, xs értéke pedig [2..] lesz. 28

3.4.3. Esetszétválasztás Az úgynevezett esetszétválasztás nagyon hasonlít a matematikai esetszétválasztásra. Például a minimum függvény definíciója { x ha x y, min(x, y) = y egyébként. ami Haskellben a következőképpen adható meg: min x y x y = x otherwise = y Az eltérések a matematikai jelölésmódtól: a kapcsos zárójel helyett függőleges vonalak vannak, a feltételek és az eredmények oszlopa fel van cserélve, a feltételek és az eredmények közt vessző helyett ott van az egyenlőségjel. Az esetek sorrendje számít, az első igaz feltételű eset határozza meg a végeredményt. Megjegyezzük hogy az otherwise nem kulcsszó, hanem a Prelude modulban definiált konstans: otherwise = True Példaként megmutatjuk még a gyorshatványozást ahol egyszerre használunk mintaillesztést és esetszétválasztást: x 0 = 1 x n even n = sqr (x (n div 2)) otherwise = x x (n 1) sqr x = x x even a Prelude-ben definiált függvény. 29

3.4.4. Lokális definíciók Lokális definíciókat függvény- vagy konstansdeklarációkhoz a where kulcsszóval tudunk bevezetni. A where hatóköre addig terjed, amíg a behúzás tart. A következő függvény az n-edik pozíciónál kettévág egy listát (0-tól számolva): splitat n xs n 0 = ([ ], xs) splitat n [ ] = ([ ], [ ]) splitat n (x : xs) = (x : as, bs) where (as, bs) = splitat (n 1) xs A lokális definíciós blokkban itt egy konstansdefiníció szerepel ami egy minta segítségével köti a rekurzív splitat hívás végeredmény párjának két elemét. Figyeljük meg, hogy ha az splitat első függvényágának egyetlen esetszétválasztásos esete nem teljesül, akkor a többi függvényág határozza meg a végeredményt. Lokális definíció nem csak deklarációkhoz kapcsolható, hanem tetszőleges kifejezéshez is. Ekkor a következő szintaxist használjuk: let minta = kifejezés in kifejezés Például a [0, 1, 2] + let xs = [3, 4, 5] + xs in xs kifejezés értéke [0, 1, 2, 3, 4, 5, 3, 4, 5, 3, 4, 5, 3, 4, 5..] lesz. 3.5. Magasabbrendű függvények Magasabbrendű függvénynek nevezzük az olyan függvényeket amik függvényt várnak vagy függvényt ad vissza. (A körrizés miatt minden többparaméteres függvény is függvényt ad vissza de csupán emiatt ezeket nem szokás magasabbrendű függvényeknek nevezni.) A magasabbrendű függvényekhez nem kapcsolódik speciális szintaxis, ugyanúgy definiálandók mint minden más függvény. A filter függvény egy adott feltétel szerint szűri a lista elemeit. A feltétel egy logikai értékű függvény, amit meghívunk az esetszétválasztáskor: 30

filter p [ ] = [ ] filter p (x : xs) p x = x : filter p xs otherwise = filter p xs A magasabbrendű függvények jól használhatók szeletekkel, például (filter ( 4) [1.. 9]) értéke [1, 2, 3, 5, 6, 7, 8, 9]. A magasabbrendű függvények szintén jól használhatók úgynevezett névtelen függvényekkel. A névtelen függvény szintaxisa: λ minta kifejezés. A kifejezés hivatkozhat a mintában definiált változókra. A névtelen függvény egy függvény értékű kifejezés. Például (filter (λx x < 4 x > 6) [1.. 9]) értéke [1, 2, 3, 7, 8, 9]. A függvénykompozíció is egy magasabbrendű függvény, amit névtelen függvény segítségével definiálunk: f g = λx g (f x) 3.6. Szigorú statikus típusozás A Haskell szigorúan és statikusan típusos nyelv. A szigorú és statikus típusozás szerepe: a futási idejű hibák minél nagyobb részének a fordítási időbe való áthelyezése, kódoptimalizációs lépések lehetővé tétele, a programozó bizonyos, a kóddal szembeni elvárásainak kifejezhetővé tétele, és ezzel a fordítóprogram által ellenőrzött dokumentáció biztosítása. A Haskell típusrendszere a Hindley-Milner típusrendszerre épül. Néhány példával szemléltetjük a típusrendszer lehetőségeit. A Haskell a következő típusokat támogatja: elemi típusok A preludeben definiált néhány elemi típus: 31

Bool: két értéke van a True és a False Char: unikód karakterek típusa Int: korlátos egész számok típusa. A korlát általában 2 31. Integer: egész számok típusa (bignum) Float, Double: egyszeres és kétszeres pontosságú lebegőpontos számok típusa A típust a :: jel után adjuk meg. Például True :: Bool x :: Char összetett típusok függvénytípus x y az x értelmezési tartományú és y értékkészletű függvények típusa. Például isalpha :: Char Bool ( ) :: Bool Bool Bool A operátor jobbra köt, azaz ( ) :: Bool (Bool Bool), ahogy a körrizés említésekor láttuk. listatípus [x], az x típusú elemeket tartalmazó listák típusa (minden elemnek azonos típusúnak kell lennie). Például [True, False] :: [Bool] [[ ], [True, False]] :: [[Bool]] n-es típus A párok típusa (x, y), ahol x a pár első elemének, y a pár második elemének típusa. Például ([True], [ x, y ]) :: ([Bool], [Char]) [(True, x ), (False, y )] :: [(Bool, Char)] Hasonló a hármasok, négyesek stb. típusa. univerzális polimorfizmus A típusban szerepelhetnek típusváltozók, amik univerzálisan kvantáltak, azaz tetszőleges típusra kicserélhetőek. Például length :: [a] Int azaz length specializálható például a [ Char] Int típusra. A specializálást a típusellenőrző automatikusan végzi. Például length [ True] esetén a típusváltozó értéke Bool lesz. 32

esetleges polimorfizmus Az univerzális polimorfizmushoz hasonló, de lehetőség van a típusváltozó lehetséges értékeinek a megszorítására. A Prelude-ben definiált néhány megszorítás (más néven típusosztály, lásd később): Num a: a lehet szám típus, például Int, Integer, Float, Double. Integral a: a lehet egész szám típus, például Int, Integer. Integral a magában hordozza a Num a megszorítást is. Eq a: az a típusra van egyenlőségvizsgálat Ord a: az a típusra van összehasonlítás művelet Ord a magában hordozza az Eq a megszorítást is. Show a: az a típus értékeket szöveggé lehet alakítani A megszorításokat egy n-esben kell elhelyezni a típus elején, a operátor előtt. Példák: even :: Integral a a a ( ) :: (Integral b, Num a) a b a max :: Ord a a a a 113 :: Num a a Vegyük észre, hogy a Haskellben vannak polimorf literálok és konstansok is. Megemlítjük, hogy a Haskellben nincs implicit típuskonverzió; minden típuskonverzió explicit, de ilyenre viszonylag ritkán van szükség. Például (even 113) esetén a 113 az alapértelmezett Integral típussal jön létre, így nincs szükség konverzióra az even alkalmazásakor. A Haskell automatikus típuskikövetkeztetést végez, azaz a függvények és kifejezések típusát nem kell megadnunk. A függvények típusát azonban célszerű megadni dokumentációs célból. Példaként nézzük meg újra a függvénykompozíció definícióját, ezúttal típusdeklarációval együtt: ( ) :: (b c) (a b) (a c) f g = λx g (f x) 33

3.7. Típusdefiníciók A Haskell típusrendszere kibővíthető típusdefiníciók segítségével. 3.7.1. Típus szinonima definíciók A típus szinonimák segítségével összetett típusnak adhatunk nevet. A Preludben például a következő definíció szerepel, ami a szövegeket karakter listákként definiálja: type String = [Char] Lehetőség van paraméteres szinonimák definiálására is: type PredicateOn a = a Bool Példa a PredicateOn használatra: isalpha :: PredicateOn Char isalpha c =... 3.7.2. Algebrai adattípusok Az algebrai adattípussal új típust hozhatunk létre. Meg kell adnunk a típus nevét és az adatkonstruktorokat. A típus nevét típuskonstruktornak is nevezzük. Az adatkonstruktorokat függőleges vonallal választjuk el egymástól. A Bool típus definíciója a Prelude-ben: data Bool = True False Külön figyelmet érdemel a következő típus: data Void = Void Ez a típus előre definiált, de speciális szintaxissal: () az azonosítója a típusés az adatkonstruktornak is, és ezt nullásnak nevezzük. A nullást más 34

típusok paraméterekét használjuk, amikor nincs szükségünk különböző értékekre. Például a természetes számok egyik lehetséges reprezentációja a [()] típus (nullások listája). A konstruktoroknak lehetnek paramétereik is, mint például a Just konstruktornak a következő definícióban: data MaybeBool = Nothing Just Bool Ekkor a Just konstruktor típusa Bool MaybeBool lesz. A típuskonstruktornak is lehetnek paraméterei, ezek kötelezően típusváltozók, mint például a Maybe típuskonstruktornak az a típusváltozó: data Maybe a = Nothing Just a Ekkor a Just konstruktor típusa a Maybe a lesz. A Maybe típust sokszor használjuk az értekezésben; a segítségével egy kivételes érték adható hozzá bármely típushoz. A konstruktorokból és változókból álló kifejezések minták, azaz szerepelhetnek a függvénydefiníciók bal oldalán. Az alábbi függvény egy függvényt alkalmaz a Maybe x értéken: fmap :: (x y) Maybe x Maybe y fmap f Nothing = Nothing fmap f (Just a) = Just (f a) A következő típus nem más mint a pár típus, csak más szintaxissal: data Pair a b -- (a, b) = Pair a b -- (a, b) Az algebrai típus lehet rekurzív is. A következő típus nem más, mint a lista típus, csak más szintaxissal: data List a -- [a] = Nil -- [] Cons a (List a) -- a : as A Cons típusa például a List a List a, és megfelel a (:) operátornak. 35

A Haskell típusellenőrző valóban nem kezeli speciálisan az n-eseket és a listákat, ezeknek csak a szintaxisa speciális. Például az [ 12, 13] kifejezést a fordító a 12:13:[ ] kifejezésként értelmezi. Ez is mutatja hogy az algebrai típusok kifejezőereje milyen nagy. Rekordok. Az algebrai adattípus konstruktor paramétereinek nevet is adhatunk, ekkor rekordról és rekordmezőkről beszélünk: data Pair a b = Pair {fst :: a, snd :: b } A rekordnak név szerint is megadhatjuk a paramétereit: Pair {fst = 1, snd = True}. A Haskellben a rekordmezőkből automatikusan mezőkiválasztó függvények készülnek, például az fst típusa Pair a b a lesz. 3.7.3. Absztrakt típusok Absztrakt típusról beszélünk, ha a típus reprezentációja rejtett. Az absztrakt típusok előnye, hogy a típus reprezentációja átfogalmazható a típust használó kódok átfogalmazása nélkül. Ha azt kívánjuk jelölni, hogy egy típus absztrakt, akkor a következő jelölésmódot használjuk: data Tree a Tree a egy egyparaméteres absztrakt típus. Ez a szintaxis nem része a Haskell nyelvnek. A Haskellben a reprezentáció elrejtésére a modulrendszere használható: például egy algebrai típusdefiníció esetén exportáljuk a típuskonstruktort, de nem exportáljuk az adatkonstruktorokat. 3.8. Típusosztályok A típusosztályok szerepe a funkcionális nyelvekben nem más, mint a programkód algoritmikus kiegészítése a programozó által megadott szabályok szerint. A típusosztályok használatával a fordítóprogram képességeit 36

egészíthetjük ki, biztonságos és kényelmes módon. A típusosztályok jól használhatók beágyazott alkalmazásspecifikus nyelvek (EDSL) írásakor, és nagyban hozzájárulnak ahhoz, hogy a Haskell kiválóan alkalmas ilyen nyelvek írására. A típusosztályok típusvezérelt kódgenerálást tesznek lehetővé, emiatt is fontos hogy a Haskell típusrendszere minél nagyobb kifejezőerővel rendelkezzen (minél szigorúbb legyen). 3.8.1. Típusosztályok definiálása Nézzük meg a Prelude-beli Show típusosztály (egyszerűsített) definícióját: class Show a where show :: a String A Show osztálynak egy a paramétere és egy show nevű metódusa van. A metódusnak nem adtunk definíciót; minden típusosztály példányban különböző definíciót adhatunk neki. Ha a kódban bárhol a show x kifejezést használjuk ahol x :: T, akkor a fordító kicseréli a show-t a Show T-n definiált példányában szereplő kódra, feltéve hogy talál megfelelő példányt. Ha a példány nem állapítható meg egyértelműen (például T polimorf), akkor maga a show x kifejezés is polimorffá válik, például show 114 :: (Show a, Num a) String esetén. A polimorf kifejezések fordítása úgynevezett könyvtár-transzformációval (dictionary) történik. A program belépési pontja (main) nem lehet polimorf. Példa osztálypéldányok megadására: instance Show Int show i =... where instance Show a Show [a] where show [ ] =... show (x : xs) =...show x... show xs... A Haskell típusosztályok erejét az adja, hogy a típusosztály példányok is lehetnek esetlegesen polimorfak, mint például a Show [ a] esetében, így minden listára egyszerre tudjuk a Show példányát definiálni. A fordító a megfelelő kódot generálja például a show [[1, 2], [ ]] kifejezéshez, holott az [[ Int]] típushoz nem adtunk meg példányt. 37

A típusosztályok közti hierarchia kialakítása úgy történik, hogy megszorításokat adunk meg az osztály (valamely) típusváltozójára az osztálydefinícióban: class Eq a where ( ) :: a a Bool class Eq a Ord a where (<) :: a a Bool 3.9. Dinamikus típusozás A Haskellben dinamikus típusozás is használható. A dinamikus típusú értékek statikus típusa közösen Dynamic. A dinamikus és a statikus típusozás közti átmenetet a következő függvények biztosítják: todyn :: Typeable a a Dynamic fromdynamic :: Typeable a Dynamic Maybe a A Typeable típusosztálynak az általánosan használt nem polimorf típusokra van példánya. fromdynamic a Nothing értékkel tér vissza, ha nem sikerült a típuskonverzió, így a használata biztonságos. Ha biztosan tudjuk, hogy a típuskonverzió a dinamikus típusról sikeres, akkor használhatjuk az Any típust is az unsafecoerce veszélyes típuskonverzióval (4.1. fejezet). 3.10. Akciók A Haskellben az akciók használatával biztosítjuk hogy a mellékhatásos számítások ne veszélyeztessék a hivatkozások behelyettesíthetőségét. A kifejezések egy meghatározott csoportját akciónak nevezzük. Az, hogy a kifejezés akció-e, a kifejezés típusa alapján egyszerűen eldönthető. Két szintet különböztetünk meg a program futása során: A kifejezések kiértékelése során sosem történik mellékhatás. Az akciók végrehajtása lehet mellékhatásos. 38

Minél több lépés történik a kiértékelés során, és minél kevesebb lépés történik a végrehajtás során, annál inkább használható a hivatkozások behelyettesíthetősége. Elmondható, hogy egy tisztán funkcionális nyelven írt program annál inkább funkcionális stílusban íródott, minél kevesebb a benne a végrehajtás. A mellékhatásokat tehát a végrehajtási fázisra korlátozzuk, így biztosítjuk a hivatkozások behelyettesíthetőségét. A nem tiszta funkcionális nyelvekben a kiértékelési és a végrehajtási fázis egybemosódik, így elvesznek a hivatkozások behelyettesíthetőségének előnyei a kiértékelési fázisban. 3.10.1. Az akciók típusa A kiértékelés és a végrehajtás időben nem különül el egymástól, a Haskell típusrendszere viszont különbséget tesz köztük. Adott egy absztrakt adattípus: data IO a Az IO a típusú értékek mellékhatásos műveletek, amik a végrehajtás során egy a típusú eredményt adnak. Példák IO típusú kifejezésekre: getline :: IO String Az getline akció egy sort olvas be a konzolról az Enter billentyű leütéséig. a :: IO () Ha az a akciónak csak a mellékhatása lényeges, akkor egy nullással (3.7.2. fejezet) tér vissza. f :: a IO b Az f egy mellékhatásmentes függvény ami akciót ad vissza. Például putchar :: Char IO () az a függvény, ami minden karakterhez hozzárendel egy akciót aminek a végrehajtása során a karakter kikerül a konzolra. a :: IO (IO Int) Az a akció egy akciót ad vissza. Ennek a konstrukciónak van értelme. Például a lehet egy akció, ami készít egy számlálót (egy Int típusú referenciát 0 kezdőértékkel), és visszaad egy olyan akciót, ami 39