Kuidas alustada Haskelli kirjutamist: Pythoni programmeerija kogemus. Süntaks kasutaja määratud funktsiooni deklareerimiseks. Automaatne mäluhaldus

Funktsionaalne programmeerimine
aastal Haskell

Loengukonspektid

Rybinsk 2010

Sissejuhatus ................................................... ...................................................... .............................. 3

1. Ajalugu funktsionaalne programmeerimine.................................................... 4

2 Funktsionaalsete keelte omadused................................................ ...................................... 5

2.1 Põhiomadused................................................ .............................................................. ........ 5

2.2 Eelised................................................ ................................................... 9

2.3 Puudused................................................................ ................................................... ......... . üksteist

3 Ülevaade olemasolevatest keeltest................................................ ...................................... 13

4 Põhiprintsiibid Haskelli keel................................................ ........................... 16

4.1 Interaktiivne keskkond................................................ ..................................................... 16

4.2 Programmi struktuur................................................ ..................................... 18

4.3 Funktsioonide tüübid.................................................. ..................................................... 22

4.4 Tingimuslikud arvutused (hargnemine)................................................ ..................... 24

4.5 Võrdlus valimiga................................................ .......................................... 27

4.6 Loendid................................................. ................................................... ...................... 29

4.7 Kohalikud määratlused............................................................................. 33

4.8 Interaktiivse keskkonna lisafunktsioonid................................... 35

4.9 Funktsioonid kõrgem järjekord......................................................................... 36

4.10 Lõpmatud andmestruktuurid................................................ .......................... 37

5 Andmetüübid ja moodulid................................................ ...................................................... .... 40

5.1 Kohandatud tüübid ja andmestruktuurid................................................ .... 40

5.2 Moodulid................................................ ................................................... ......... ...... 44

6 Klassid ja monaadid................................................ ...................................................... ............... 47

6.1 Klassid................................................. ................................................... ......... ....... 47

6.2 I/O................................................ ...................................................... ........ .. 49

7 Näited................................................. ................................................... ...................... 53

Järeldus................................................................ ................................................... .......... 54

Kasutatud allikate loetelu................................................ ...................................... 55

Sissejuhatus

Enne funktsionaalse programmeerimise enda kirjeldamist pöördugem programmeerimise ajaloo juurde üldiselt. 1940. aastatel esimene digitaalsed arvutid, mis programmeeriti erinevate lülitite, juhtmete ja nuppude ümberlülitamisega. Selliste lülitite arv ulatus mitmesajani ja kasvas programmide keerukamaks muutudes. Seetõttu oli programmeerimise arendamise järgmine samm igasuguste lihtsa mnemoonikaga montaažikeelte loomine.

Kuid isegi monteerijatest ei saanud tööriista, mida paljud inimesed saaksid kasutada, kuna mnemokoodid olid endiselt liiga keerulised ja iga monteerija oli tihedalt seotud arhitektuuriga, millel see käivitati. Järgmine samm pärast assemblerit olid nn imperatiivsed keeled kõrge tase: BASIC, Pascal, C, Ada ja teised, sealhulgas objektorienteeritud. Selliseid keeli nimetatakse imperatiivseteks (“preskriptiivseteks”), kuna need on keskendunud mäluga (st ülesanded) ja iteratiivsete tsüklitega töötavate käskude järjestikusele täitmisele. Funktsiooni- ja protseduurikutsed, isegi rekursiivsed, ei vabastanud selliseid keeli selgesõnalisest imperatiivsusest.

Funktsionaalse programmeerimise paradigmas on nurgakiviks funktsioon. Matemaatilised funktsioonid väljendavad seost mõne protsessi algandmete ja lõpptoote vahel. Arvutusprotsessil on ka sisend ja väljund, seega on funktsioon täiesti sobiv ja adekvaatne vahend arvutuste kirjeldamiseks. Just see lihtne põhimõte on funktsionaalse paradigma ja funktsionaalse programmeerimisstiili aluseks. Funktsionaalne programm on funktsioonide määratluste kogum. Funktsioonid defineeritakse teiste funktsioonide kaudu või rekursiivselt nende endi kaudu. Funktsionaalses keeles on programmeerijal vaja ainult soovitud tulemust funktsioonide süsteemina kirjeldada.

2 Funktsionaalse programmeerimise ajalugu

Nagu teada, teoreetiline alus Imperatiivse programmeerimise asutasid 1930. aastatel Alan Turing ja John von Neumann. Funktsionaalse lähenemise aluseks olev teooria sündis samuti 20-30ndatel. Funktsionaalse programmeerimise matemaatiliste aluste arendajate hulgas on kombinatoorset loogikat arendanud Moses Sheinfinkel ja Haskell Curry, aga ka λ-arvutuse (lambdaarvutuse) looja Alonzo Church.

Teooria jäi teooriaks, kuni John McCarthy töötas 1950. aastate alguses välja Lispi, millest sai esimene peaaegu funktsionaalne programmeerimiskeel ja jäi paljudeks aastateks ainsaks. Lisp on endiselt kasutusel, pärast aastatepikkust arengut rahuldab see tänapäevased vajadused.

Tarkvara keerukuse suurenemise tõttu hakkab trükkimine mängima järjest olulisemat rolli. 20. sajandi 70ndate lõpus ja 80ndate alguses töötati intensiivselt välja funktsionaalsete keelte jaoks sobivaid tippimismudeleid. Enamik neist mudelitest sisaldas toetust sellistele võimsatele mehhanismidele nagu andmete abstraktsioon ja polümorfism. Ilmub palju trükitud funktsionaalseid keeli: ML, Scheme, Hope, Miranda, Clean ja paljud teised. Lisaks suureneb pidevalt murrete arv. Peaaegu iga funktsionaalne programmeerimisrühm kasutas oma keelt. See takistas nende keelte edasist levikut ja tõi kaasa palju väiksemaid probleeme. Olukorra parandamiseks otsustas funktsionaalse programmeerimise valdkonna juhtivate teadlaste ühine rühm luua erinevate keelte tugevad küljed uues universaalses funktsionaalses keeles. Selle keele esimene rakendus, Haskell Curry järgi nime saanud Haskell, loodi 90ndate alguses.

3 Funktsionaalsete keelte omadused

Nagu igal programmeerimiskeelte rühmal, on ka funktsionaalsetel keeltel mõned omadused, mis eristavad neid teistest keeltest ja muudavad need funktsionaalseteks programmeerimiskeelteks.

3.1 Põhiomadused

Funktsionaalsete keelte omaduste hulgas eristatakse tavaliselt järgmist:

– lühidus ja lihtsus;

– range trükkimine;

– funktsioonid on väärtused;

– puhtus (kõrvalmõjudeta);

– edasilükatud (laiskad) arvutused;

– modulaarsus.

Vaatame kõiki neid omadusi üksikasjalikumalt.

Lühidus ja lihtsus

Funktsionaalsetes keeltes olevad programmid on tavaliselt lühemad ja lihtsamad kui samad programmid kohustuslikes keeltes. Üks levinumaid näiteid on Hoare kiirsorteerimine.

Peal kohustuslik keel C kiirsortimist rakendatakse tavaliselt järgmiselt:

void qsort(int a, int l, int r)

int i = l, j = r, x = a[(l + r) / 2];

samas (a[i]< x) i++;

while(x< a[j]) j--;

sisetemperatuur = a[i];

kuni ma<= j);

if(l< j) qsort (a, l, j);

kui ma< r) qsort (a, i, r);

Haskelli funktsionaalkeeles on see sama sorteerimine kirjutatud palju lühemalt ja selgemalt:

qsort(x:xs) = qsort

++[x]++qsort

Seda näidet tuleks lugeda nii: kui sorteeritav loend on tühi, siis on ka sortimise tulemuseks tühi loend, vastasel juhul on loendi pea ja saba (loendi esimene element ja ülejäänud elementide loend , võib-olla tühjad) on valitud ja tulemuseks on kõigi peast väiksemate sabaelementide sorteeritud loendi liitmine (liitmine), pea enda loend ja kõigi sabaelementide loend, mis on suuremad kui või võrdne peaga.

Funktsionaalses keeles olev kood on kasulik koodi suuruse ja selguse osas. Lisaks sorteerib ülaltoodud C-rakendus massiivi, mille elemendid on täisarvu tüüpi (int), samas kui Haskelli teostus saab sortida mis tahes tüüpi elementide loendeid, millel võrdlusoperatsioon on määratletud.

Tugev trükkimine

Enamik funktsionaalseid programmeerimiskeeli on tugevasti trükitud. Range tippimine tähendab järgmiste kohustuslike tingimuste täitmist:

– funktsiooni iga väärtus, muutuja, argument ja tagastatav väärtus programmi kavandamise etapis on tingimusteta seotud konkreetse andmetüübiga, mida ei saa programmi täitmise ajal muuta;

- funktsioonid võivad aktsepteerida ja tagastada väärtusi, millel on funktsiooni kirjelduses täpselt sama tüüpi andmetüüp;

– iga toiming nõuab rangelt määratletud tüüpi argumente;

– kaudne tüübiteisendus ei ole lubatud (st tõlkija tajub veana katset kasutada muud tüüpi väärtust kui muutuja, argumendi, funktsiooni või toimingu puhul kirjeldatud).

Programmeerimise teoorias on range tippimine arendatud tarkvara töökindluse tagamisel asendamatu element. Õige kasutamise korral (see tähendab, et programm deklareerib ja kasutab loogiliselt mitteühilduvate väärtuste jaoks eraldi andmetüüpe), kaitseb see programmeerijat lihtsate, kuid raskesti tuvastatavate vigade eest, mis on seotud loogiliselt mitteühilduvate väärtuste jagamisega, mis mõnikord tulenevad lihtsalt elementaarsest kirjaveast. Sellised vead tuvastatakse isegi programmi koostamise etapis, samas kui on võimalik kaudselt teisendada peaaegu kõik tüübid üksteiseks (nagu näiteks klassikalises C-keeles), tuvastatakse need vead ainult testimise ajal, mitte kõik. neist ja mitte korraga.

Toimib väärtustena

Funktsionaalsetes programmeerimiskeeltes saab funktsioone kasutada nagu kõiki teisi objekte, neid saab argumentidena teistele funktsioonidele edastada, teiste funktsioonide tulemusena tagastada ning salvestada loenditesse ja muudesse andmestruktuuridesse. Funktsioone, mis võtavad muid funktsioone argumentidena või tagastavad tulemusi, nimetatakse kõrgemat järku funktsioonideks.

Kõrgema järgu funktsioonide kasutamine võimaldab luua paindlikumaid funktsioone, suurendades seeläbi koodi taaskasutust. Tavaliselt tuuakse näitena elementide võrdlusfunktsiooni edastamine sortimisfunktsioonile.

Puhtus

Puhtus on funktsiooni väärtuste arvutamisel kõrvalmõjude puudumine. Funktsiooni kõrvalmõjuks on võime selle arvutuste arvutamise käigus lugeda ja muuta globaalsete muutujate väärtusi, sooritada I/O toiminguid, reageerida erandolukordadele ja muuta sisendmuutujate väärtusi. . Seega, kui kutsute sellist funktsiooni kaks korda sama sisendargumendi väärtuste komplektiga, võivad arvutustulemused erineda ja muutuda ka mõne globaalse objekti (näiteks muutujaväärtuste) olek. Selliseid funktsioone nimetatakse kõrvalmõjudega mittedeterministlikeks funktsioonideks.

Puhtalt funktsionaalses programmeerimises annab sama funktsioon samade argumentidega alati sama tulemuse. Loodud objekte ei saa muuta ega hävitada, uusi saab luua ainult olemasolevate põhjal. Tänu puhtusele ei muutu programmid mitte ainult selgemaks, vaid lihtsustatakse ka paralleelsuse korraldamist, kuna funktsioone saab arvutada üksteisest sõltumatult. Kui puhta funktsiooni tulemust ei kasutata, võib selle hindamise ära jätta ilma teisi avaldisi kahjustamata. Ja kui kahe puhta funktsiooni vahel andmete sõltuvus puudub, siis saate muuta nende arvutamise järjekorda või arvutada need paralleelselt. Sel juhul võib koostaja kasutada mis tahes hindamispoliitikat. See annab koostajale vabaduse programmis väljendite hindamist kombineerida ja ümber korraldada.

Laisad arvutused

Traditsioonilistes keeltes arvutatakse enne funktsiooni kutsumist kõigi selle argumentide väärtused. Seda funktsioonide kutsumise meetodit nimetatakse "kõne väärtuse järgi". Kui mõnda argumenti ei kasutata, tehti arvutused asjata. Paljud funktsionaalsed keeled kasutavad funktsioonide helistamiseks teistsugust meetodit - "helista vastavalt vajadusele". Sel juhul hinnatakse iga funktsiooni argumenti ainult siis, kui selle väärtus on funktsiooni tulemuse arvutamiseks vajalik. Näiteks C++ sideoperaator (&&) ei nõua teise argumendi hindamist, kui esimene on väär.

Laisk hindamine võimaldab mõnel juhul kiirendada programmi täitmist ja võimaldab kasutada ka erinevaid lõpmatuid andmestruktuure.

Modulaarsus

Modulaarsusmehhanism võimaldab jagada programmid mitmeks suhteliselt iseseisvaks osaks (või mooduliks), mille vahel on selgelt määratletud ühendused. See lihtsustab suurte tarkvarasüsteemide projekteerimise ja hilisema toetamise protsessi. Modulaarsuse tugi ei ole funktsionaalsete programmeerimiskeelte omadus, kuid seda toetab enamik selliseid keeli.

3.2 Kasu

Funktsionaalsetel programmeerimiskeeltel on kohustuslike keelte ees mitmeid eeliseid. Funktsionaalse programmeerimise paradigma kasutamine suurendab programmide usaldusväärsust, nende kirjutamise kiirust, ühikutestimise mugavust, optimeerimise võimalust kompileerimise ajal ja paralleelsuse automaatse organiseerimise võimalust.

Programmeerimise usaldusväärsus

Kõrvalmõjude puudumine muudab võimatuks paljud raskesti tuvastatavad vead, näiteks globaalsele muutujale kogemata vale väärtuse määramise. Range staatiline tippimine võimaldab teil kompileerimisetapis tabada suure hulga vigu, mis on seotud teatud andmete ebaõige kasutamisega.

Funktsionaalsete keelte huvitav omadus on see, et nad sobivad matemaatiliseks analüüsiks. Kuna funktsionaalne keel on lihtsalt formaalse süsteemi teostus, siis kõik matemaatilised toimingud, mida saaks teha paberil, kehtivad ka sellises keeles kirjutatud programmide kohta. Näiteks saab kompilaator teisendada koodilõigud samaväärseteks, kuid tõhusamateks juppideks, tõestades matemaatiliselt juppide samaväärsust. Lisaks saate nende tehnoloogiate abil tõestada, et teie programmi teatud osad on õiged.

Ühiku testimise mugavus

Kuna funktsionaalse programmeerimise funktsioon ei saa tekitada kõrvalmõjusid, ei saa objekte muuta ei ulatuse sees ega väljaspool (erinevalt imperatiivsetest programmidest, kus üks funktsioon võib muuta mõnda välist muutujat, mida loeb teine ​​funktsioon). Funktsiooni hindamise ainus mõju on selle tagastatav tulemus ja ainus tegur, mis tulemust mõjutab, on argumentide väärtused.

Nii on võimalik testida programmi iga funktsiooni, lihtsalt hinnates seda erinevate argumentide väärtuste komplektide põhjal. Sel juhul ei pea te muretsema funktsioonide õiges järjekorras kutsumise või välise oleku õige moodustamise pärast. Kui mõni programmi funktsioon läbib ühikutestid, võite olla kindel kogu programmi kvaliteedis. Imperatiivsetes programmides ei piisa funktsiooni tagastusväärtuse kontrollimisest: funktsioon võib muuta välist olekut, mida tuleb samuti kontrollida, mida aga funktsionaalsetes programmides teha ei tohiks.

Optimeerimisvalikud

Programmi kirjeldus funktsioonide komplekti kujul ilma nende arvutamise järjekorda ja funktsioonide puhtust selgesõnaliselt märkimata võimaldab rakendada funktsionaalprogrammidele üsna keerukaid ja tõhusaid automaatse optimeerimise meetodeid.

Funktsionaalsete programmide eeliseks on ka see, et need pakuvad laialdasi võimalusi arvutuste automaatseks paralleelseerimiseks. Kuna kõrvalmõjude puudumine on garanteeritud, on igas funktsioonikutses alati võimalik paralleelselt hinnata kahte erinevat argumenti – nende hindamise järjekord ei saa kõne tulemust mõjutada.

Funktsiooni omaduste tõend

Kuna funktsioonide arvutamine funktsionaalses programmeerimises ei põhjusta kõrvalmõjusid, on selliste funktsioonide analüüsimisel rakendatavad matemaatilised meetodid (näiteks matemaatilise induktsiooni meetod). See võimaldab ilma testimiseta tõestada funktsioonide või nende mõne muu omaduse õiget toimimist.

3.3 Puudused

Funktsionaalse programmeerimise puudused tulenevad samadest funktsioonidest. Ülesannete puudumine ja nende asendamine uute andmete genereerimisega toob kaasa vajaduse pideva mälu eraldamise ja automaatse vabastamise järele, seetõttu muutub ülitõhus prügikoguja funktsionaalse programmi täitmissüsteemi kohustuslikuks komponendiks. Et prügivedu oleks tõhus, tuleb jälgida andmeviiteid, mis nõuab samuti aega ja mälu. Tööstuslike programmide tsüklite puudumine on üsna tõsine piirang, kuna paljud algoritmid nõuavad väga pikki või isegi formaalselt lõpmatuid silmuseid, mille esitamine rekursiivsel kujul on ebaefektiivne või isegi võimatu rekursiooni suure nõutava sügavuse tõttu. Mingil määral saab viimasest puudusest mööda hiilida, teisendades rekursiooni automaatselt tsükliks, mille teostavad mõned funktsionaalsete keelte tõlkijad sabarekursiooni konkreetsel juhul, kuid mitte kõik rekursiooni vormid ei võimalda sellist teisendust (aga need, mis ei allu sellisele teisendamisele, ei saa kujundada lihtsa tsüklina ja kohustuslikes keeltes).

Nende puuduste ületamiseks sisaldavad paljud funktsionaalsed keeled hädavajalikke programmeerimisfunktsioone, mis võimaldavad toimingute, tsüklite ja kõrvalmõjude (nt sisend- ja väljundtoimingud) selgesõnalist järjestamist.

4 Olemasolevate keelte ülevaade

Praegu on välja töötatud suur hulk funktsionaalseid programmeerimiskeeli. Neil kõigil on ainulaadsed omadused, positiivsed ja negatiivsed omadused. Tänapäeval on kõige levinumad ja tõhusamad keeled järgmised keeled või keelte perekonnad:

Vaatame kõigi nende keelte funktsioone:

Lisp. Sai oma nime ingliskeelsest LIST Processing - loenditöötlusest. Lisp on üks varasemaid funktsionaalseid programmeerimiskeeli. Programmid ja andmed Lispis on esindatud lineaarsete sümbolite loendite süsteemidega. Lisp koos Adaga läbis sõjalise ja tööstusliku kasutuse põhjaliku standardimisprotsessi, mille tulemuseks oli Common Lisp standard. Rakendused on enamiku platvormide jaoks olemas. Lispi esimesed rakendused olid sümboolsetes andmetöötlus- ja otsustusprotsessides. Tänapäeval populaarseim murre Common Lisp on universaalne programmeerimiskeel. Seda kasutatakse laialdaselt mitmesugustes projektides: Interneti-serverites ja -teenustes, rakendusserverites ja andmebaasidega suhtlevates klientides, teaduslikes arvutustes ja mänguprogrammides.

Haskell. See on üks levinumaid laisk programmeerimiskeeli. Sellel on väga arenenud tippimissüsteem. Viimasel ajal on rakendusteekide komplekt täienenud, keelt integreeritakse levinud tarkvarasüsteemidesse, mis muudab keele professionaalsetele programmeerijatele üha atraktiivsemaks. Keele põhijooned: lambda-abstraktsiooni kasutamise oskus; kõrgema järgu funktsioonid; osaline rakendamine; kõrvalmõjude lubamatus (keele puhtus); laisk hindamine; mustrite sobitamine, funktsionaalsed mustrid (mustri sobitamine); parameetriline polümorfism ja tüübiklassi polümorfism; staatiline tippimine; automaatne tüübijäreldamine (Hindley–Milneri trükkimismudeli alusel); algebralised andmetüübid; andmetüübid koos parameetritega; rekursiivsed andmetüübid; abstraktsed andmetüübid (kapseldamine); loetle mõistmised; valvurväljendid; võimalus kirjutada kõrvalmõjudega programme ilma funktsionaalse programmeerimise paradigmat rikkumata, kasutades monaadid; võimalus integreerida kohustuslikes programmeerimiskeeltes rakendatud programmidega avatud liideste kaudu (võõrfunktsiooni liidese keele standardlaiendus).

M.L.(Meta Language) on rangete funktsionaalsete programmeerimiskeelte perekond, millel on välja töötatud polümorfse tüüpi süsteem ja parameetrid, mida saab seadistada. ML-i õpetatakse paljudes Lääne ülikoolides. Tugevalt trükitud keel staatilise tüübikontrolli ja rakendusliku programmi täitmisega. ML-i peamised eelised on programmi kõrge kontrollitavus, silumise lihtsus, suure optimeerimise potentsiaal ja salvestamise ainulaadne lühidus. Peamised puudused on süntaksi keerukus, aktsepteeritud tavade ja piirangute tundmatus ning makroteisenduste praktiline võimatus.

Erlang– funktsionaalne programmeerimiskeel, mis võimaldab kirjutada programme erinevatele hajutatud süsteemidele. Arendanud ja toetanud Ericsson. Keel sisaldab tööriistu paralleelsete protsesside loomiseks ja nende suhtlemiseks asünkroonsete sõnumite saatmise teel. Programm tõlgitakse baitkoodiks, mida käivitab virtuaalmasin, mis tagab kaasaskantavuse. Erlangi peamine asi on selle kerge protsessimudel. Erlangi loosungi "Kõik on objekt" parafraseerimiseks võime öelda: "Kõik on protsess". Protsessid on odavad; protsessi loomine ei võta rohkem ressursse kui funktsiooni kutsumine. Ainus viis, kuidas protsessid saavad suhelda, on asünkroonne sõnumivahetus.

Loetletud keeltest on funktsionaalsete programmeerimiskeelte silmapaistvaim esindaja Haskell. Sellel on lihtne süntaks ja kõik ülaltoodud funktsionaalsete keelte omadused.

5 Haskelli keele põhiprintsiibid

Nagu öeldud, on Haskelli programmeerimiskeel funktsionaalne keel ja sellel on kõik ülaltoodud omadused

Kõigepealt vaatame tööks vajalikke tööriistu. Tänapäeval on kõige levinum ja tõhusam kompilaator G.H.C.(Glasgow Haskelli koostaja). Seda levitatakse avatud litsentsi alusel ja igaüks saab selle lähtekoodi või populaarsete operatsioonisüsteemide jaoks kompileeritud versiooni alla laadida ametlikult veebisaidilt http://haskell. org/ghc/ (lisaks on keele kohta palju lisateavet aadressil http://haskell.org/).

Lisaks kompilaatorile endale sisaldab GHC interaktiivset keskkonda GHCi (GHC interactive) – Haskelli interpretaatorit, mis võimaldab hinnata mis tahes väljendeid ja tõlgendada kirjutatud programme.

Kahjuks pole Haskelli jaoks veel välja töötatud täisfunktsionaalne arenduskeskkond (välja arvatud võib-olla Haskelli keeles kirjutatud Haskelli arenduskeskkond Leksah ja mõned Visual Studio ja Eclipse'i pistikprogrammid), vaid sageli ainult täiustatud tekstiredaktor (näiteks Notepad++, gedit, kate) süntaksi esiletõstmise ja mõne muu funktsiooniga.

5.1 Interaktiivne keskkond

Interaktiivne keskkond GHCi saab hinnata mis tahes Haskelli avaldist. Vaatame selle keskkonnaga töötamise põhitõdesid. Selle käivitamiseks (pärast GHC või Haskell-Platformi installimist) lihtsalt käivitage konsoolis ghci programm (või valige kõigi programmide loendist sobiv programm). Pärast käivitamist ilmub konsoolile viip:

Prelude tähendab aktiivse mooduli nime, vaikimisi laaditakse tavaline Prelude moodul, mis sisaldab kõiki põhifunktsioone ja andmetüüpe.

Siin saame hinnata mis tahes avaldist, näiteks tavalist aritmeetilist avaldist:

Nagu näeme, on interaktiivne keskkond avaldise tulemuse välja arvutanud ja selle väljastanud uus rida. Proovime arvutada keerulisema avaldise:

Prelüüd> 1-2*(4-3^2)

Tõstmine astmeni (^) on tavaline operaator, mis on määratletud standardmoodulis Prelude koos liitmise ja korrutamise operatsioonidega.

Välja arvatud aritmeetilised avaldised interaktiivses keskkonnas on võimalik hinnata mis tahes muid standardmoodulis või mis tahes muus kasutaja poolt laaditud funktsioone.

Funktsiooni väärtuse arvutamiseks kirjutage selle nimi ja täpsustage argumendid, eraldades need tühikutega. Näiteks standardmoodulis on see defineeritud max funktsioon, valides maksimaalne väärtus kahest argumendist. Saate seda kasutada järgmiselt:

Prelüüd> max 7100

IN selles näites Arvutatakse maksimaalselt kaks numbrit - seitse ja sada. Nagu näeme, on funktsiooni arvutamise tulemuseks arv 100.

Funktsiooni argumendid võivad olla mis tahes avaldised (kuid ainult sobivat tüüpi; tüüpe käsitletakse üksikasjalikumalt teistes jaotistes), näiteks:

Prelüüd>max (2^^3)

See näide määratleb, et kaks kümnenda astmeni on suurem kui kümme kolmanda astmeni. Pange tähele, et argumentide lisamine sulgudesse on vajalik selleks, et esile tuua, milline avaldise osa on argument. Näiteks kui jätame teise argumendi sulud välja, saame veidi ootamatu tulemuse:

Prelüüd> max (2^10) 10^3

Ilma sulgudeta see väljend tõlgendatakse kahe arvu (1024 ja 10) maksimumina, mis on tõstetud kolmandasse astmesse.

Lisaks saab GHCi interaktiivne keskkond sisestatud funktsioonide nimed automaatselt täiendada. Kui sisestate ainult funktsiooni nime algosa ja vajutate klaviatuuril klahvi Tab, proovib GHCi täiendada funktsiooni nime saadaolevate definitsioonide hulgast saadaoleva (standardmooduli või kasutaja poolt ühendatud) nimetuseni. Näiteks kui sisestate "maxi" ja vajutate tabeldusklahvi, täiendab GHCi funktsiooni nime sõnaga "maximum". Juhul, kui ühemõtteliselt täiendada pole võimalik (sobivaid valikuid on mitu), kuvatakse kõik võimalikud valikud:

max max Piiratud maksimum

Nüüd saate funktsiooni nime täpsustada (lisates paar tähte) ja vajutada uuesti klahvi "Tab".

Automaatne täitmine on väga kasulik, kui kasutate palju pikkade nimedega funktsioone.

5.2 Programmi struktuur

Haskelli kompilaatorid ja tõlgid töötavad laiendiga failidega *.hs mis sisaldab programmi teksti. Programmi teksti struktuur on järgmine:

1. programmi alguses saab märkida aktiivse mooduli nime ja eksporditud definitsioonid;

3. Ülejäänud osa programmi hõivavad erinevad definitsioonid – funktsioonide, andmetüüpide ja klasside definitsioonid.

Lihtsaim programm võib sisaldada ainult funktsioonide määratlusi (sel juhul imporditakse ainult standardne Prelude moodul, mis sisaldab enamikku standardfunktsioonidest ja andmetüüpidest; mooduli nimeks on vaikimisi seatud Main).

Lihtsaim funktsioon ei pruugi üldse argumente võtta, vaid tagastab ainult väärtuse. Sel juhul koosneb definitsioon funktsiooni nimest, võrdusmärgist “=” ja mõnest avaldisest, millest selle funktsiooni väärtus arvutatakse. Tavapäraselt võib sellist funktsiooni nimetada muutujaks või konstandiks, kuid see pole päris õige. Funktsionaalses programmeerimises nimetatakse ilma argumentideta funktsioone sümboliteks. Vaatame näidet:

See näide deklareerib sümboli nimega viis, mille väärtus on täisarv 5. Haskelli nimed on tõstutundlikud, mis tähendab, et viis ja viis on erinevad nimed. Lisaks tutvustatakse seda täiendav piirang nime esitäheni - funktsioonide nimed ja nende argumendid võivad ainult alata väiketäht(viis, max, min, x, y) ning andmetüüpide (Bool, Integer, Double), moodulite (Main, Test) ja klasside (Eq, Ord, Num) nimed on ainult suurtähtedega.

Vaatame keerulisemat näidet:

kolm = üks + kaks

Siin on deklareeritud kolm sümbolit - üks, kaks, kolm. Nagu näitest näha, võtab iga definitsioon ühe rea ja neid eraldab ainult rea lõpp (tühja ridu eiratakse). Sümbolid üks ja kaks on määratletud samamoodi nagu eelmises näites sümbol viis ning sümboli kolm definitsioonis kasutatakse olemasolevaid definitsioone. Nagu võite arvata, on sümbolil kolm väärtust 3.

Määratluste järjekord ei ole oluline, see tähendab, et järgmine näide on eelmisega täiesti sarnane:

kolm = üks + kaks

Laadime oma näite GHCi interaktiivsesse keskkonda. Selleks piisab, kui ghci käivitamisel (OS-is) määrata käsurea parameetriks faili nimi koos programmi tekstiga (näiteks Test. hs). Windowsi perekond Peate lihtsalt faili avama, installitud GHC määrab automaatselt *.hs-failide avamiseks ghci). Kui programm ei sisalda vigu, näeme juba tuttavat viipa:

Siin Main on aktiivse mooduli nimi (moodulitest on täpsemalt juttu vastavas peatükis). GHCi võimaldab arvutada mis tahes funktsioone praegusest moodulist. Näiteks arvutame oma sümboli kolm:

Rohkem keerulised väljendid võimalik ka:

*Põhi> (kolm+kaks)^2

*Põhi>max üks kaks

Järgmisena vaatame argumentidega funktsioone. Erinevalt tavapärastest programmeerimiskeeltest ei pea argumentide edastamine neid sulgudesse kirjutama ja komadega eraldama. Funktsiooni kutsutakse järgmisel kujul: func x1 x2 x3... xN, kus func on funktsiooni nimi ja xi on i-s argument. Funktsiooni tulemuseks on mõni objekt, näiteks arv, loend, funktsioon, lambda-avaldis või mõni muu andmestruktuur.

Funktsiooni kirjeldus argumentidega praktiliselt ei erine eelmiste näidete sümbolite kirjeldusest. Funktsiooni definitsioon asub eraldi real ja sellel on järgmine vaade: func x1 x2 x3… xN = avaldis, kus func on uue funktsiooni nimi, xi on i-nda argumendi nimi, avaldis on avaldis.

Näiteks lisame funktsiooni, mis lisab kaks numbrit olemasolevat faili Test. hs.

Nüüd saame muudetud mooduli interaktiivses keskkonnas uuesti laadida. Selleks taaskäivitage ghci või kasutage seda standardne käsk":r":

Põhiline kompileerimine (test. hs, tõlgendatud)

Ok, moodulid laaditud: Main.

Pärast seda uus funktsioon muutub interaktiivsest keskkonnast juurdepääsetavaks:

*Põhi> pluss üks 8

Veel üks näide argumentidega funktsioonist:

Iga definitsioon võtab enda alla ühe rea, kuid kui selguse huvides on mugavam definitsioon mitmeks reaks jagada, saate kasutada järgmist funktsiooni: iga rida, mis algab eelmisest suurema taandega, loetakse selle jätkuks. Näiteks võime plussfunktsiooni kirjutada kahele reale järgmiselt:

Haskelli üherealised kommentaarid algavad kahe kriipsuga:

pluss x y = x+y --liitmise funktsioon

Blokeeritud kommentaar algab tähega "" ja lõpeb tähega "":

see funktsioon tagastab 1 võrra suurema arvu

kui argumendina saadud

5.3 Funktsioonide tüübid

Haskell on tugevasti tüüpiline keel, mis tahes funktsioonil selles on rangelt määratletud tüüp. Kuid eelmistes näidetes me tüüpi ei täpsustanud, kuna Haskelli tõlgid ja kompilaatorid rakendavad võimsat tüübi järeldamismehhanismi. Funktsioonide tüübi (ja nende argumendid) saab enamikul juhtudel tuletada nende definitsioonidest. Mõnel funktsioonil võib olla polümorfset tüüpi (selliseid funktsioone saab rakendada erinevat tüüpi argumentidele), näiteks võib eelnevalt antud plussfunktsioon liita täisarvud ja reaalarvud.

Nagu varem öeldud, algavad tüübinimed suurtähtedega. Siin on mõned standardtüübid:

Kirjeldus

täisarv alates – kuni

pikk täisarv, ilma piirideta (pakutakse sisseehitatud pikka aritmeetikat)

tegelik arv

topelttäpsusega reaalarv

mingit tüüpi a elementide loend, näiteks täisarvude loend, mis on kirjutatud kujul

string (või märkide loend), samaväärne

boolean tüüpi(võtab vastu Tõelised väärtused või vale)

kahe a- ja b-tüüpi elemendi korteež (näiteks (String, Bool))

kolmest a-, b- ja c-tüüpi elemendist koosnev korteež (näiteks (String, Bool, Int))

Kui programmeerija soovib ise näidata funktsiooni tüüpi ja selle argumente või selle funktsiooni tüübi automaatne tuletamine on võimatu, peab ta seda tegema täiendav määratlus, kasutades spetsifikatsioonioperaatorit, näiteks "::". Näiteks reaalväärtuse tagastava funktsiooni kirjeldamiseks võite kirjutada järgmise koodi:

Esimene rida tähendab, et funktsioon pi on tüüpi Double, seejärel tuleb funktsiooni definitsioon ise, mis tähendab, et funktsioon pi tagastab pi ligikaudse väärtuse. Kui funktsioonil on üks argument, kirjeldatakse selle tüüpi järgmiselt:

inc::Integer -> Integer

See märge tähendab, et funktsioon inc teisendab Integer tüüpi argumendi tulemuseks Integer.

Kui funktsioonil on kaks argumenti, näeb selle kirjeldus välja järgmine:

võimsus:: Double -> Integer -> Double

Võimsuse funktsioon võtab kaks argumenti – reaalbaas x ja täisarv võimsus n ning funktsiooni hindamise tulemuseks on reaalarv.

IN üldine vaade Funktsiooni tüübi kirjeldus näeb välja järgmine:

nimi:: X1 -> X2 -> ... ->XN -> Y

Siin on nimi funktsiooni nimi, Xi on i-nda argumendi tüüp, Y on funktsiooni tüüp.

Nagu varem mainitud, on tüüpide määramine valikuline, kuid selle olemasolu võib pakkuda täiendavat kontrolli funktsiooni deklaratsioonis konstrueeritud avaldiste õigsuse üle. Lisaks on mõnel juhul, kui kasutatakse polümorfseid tüüpe, võimatu määrata funktsiooni tüüpi ilma tüüpi selgesõnaliselt määramata.

Loetleme mõned standardfunktsioonid.

Funktsioonid

Kirjeldus

traditsiooniline aritmeetilised tehted

reaalarvude jagamine

arvu tõstmine positiivse täisarvu astmeni

numbri tõstmine tõeliseks võimsuseks

täisarvu jagamine ja jääk täisarvude jagamisel

Ruutjuur

trigonomeetrilised funktsioonid

asin, acos, atan

trigonomeetrilised pöördfunktsioonid

võrdsuse ja ebavõrdsuse võrdlus

>, <, >=, <=

võrdlus

loogilisi tehteid

paari esimene element (kahe elemendi korrutis)

paari teine ​​element

loendi saba (kõik elemendid, välja arvatud esimene).

5.4 Tingimuslikud arvutused (hargnemine)

Traditsioonilistes programmeerimiskeeltes on hargnemise korraldamise peamised viisid tingimuslause (kui siis muidu) ja valikulause (case või switch). Lisaks neile kasutab Haskell funktsioonide definitsioonides ja nn valveavaldistes mustrite sobitamist. Vaatame kõiki neid meetodeid üksikasjalikumalt.

kui-siis-muidu konstruktsioon

Süntaksikonstruktsioon "if-then-else" võimaldab teil hinnata erinevaid väljendeid sõltuvalt mõne tingimuse tulemustest:

kui<условие>siis<выражение1>muidu<выражение2>

Siin<условие>- mõni Bool-tüüpi väljend. Erinevalt imperatiivsetest keeltest on Haskellis konstruktsioon "kui-siis-muu" avaldis, millel peab olema mingi tulemus. Sellega seoses on muu haru kohustuslik ja avaldiste tüübid<выражение1>Ja<выражение2>peab sobima.

Vaatleme näiteks kahe arvu maksimaalse arvutamise funktsiooni:

max a b = kui a>b siis a else b

Nagu juba öeldud, on if-then-else konstruktsioon avaldis, millel on tulemus. Seetõttu saab seda kasutada mõne muu väljendi osana:

*Main> 5 + kui Väär, siis 1 muu 0

*Põhi> (kui Tõene, siis 1 muu 0) + 5

Pange tähele, et viimases näites on sulud kohustuslikud. Ilma sulgudeta tõlgendatakse väljendit erinevalt:

*Main> kui Tõene, siis 1 muu 0 + 5

Kõik, mis on kirjutatud pärast sõna “muu”, viitab muu haru väljendusele.

juhtumi konstruktsioon

Vaatleme näiteks arvutusfunktsiooni antud number Fibonacci:

fib n = juhtum n of

_ -> fib (n-1) + fib (n-2)

Nii nagu if-then-else tingimusavaldisel, on ka käändeavaldisel tulemus ja seetõttu võib see olla osa teistest avaldistest.

Vaatleme juhtumiväljendust üldisel kujul:

juhtum<выражение0>kohta

<образец1> -> <выражение1>

<образец2> -> <выражение2>

<образецN> -> <выражениеN>

Siin on arvutuse tulemus<выражение0>sobitatakse järjestikku mustritega. Kui see on edukalt sobitatud i-s proov, on kogu juhtumi tulemus i-nda avaldise tulemus. Mustri sobitamisest tuleb täpsemalt juttu vastavas osas, kuid praegu võib seda vaadelda kui võrdlust etteantud konstantidega.

Pange tähele, et iga näidis tuleb kirjutada uuele reale, mille taane on taandest suurem reserveeritud sõna juhtum. Samuti peavad näidiste ees olevad taanded olema üksteisega võrdsed.

Lisaks on vastuvõetav alternatiivne viis käändeavaldiste kirjutamiseks ilma taanet kasutamata:

fib n = (1->1;2->1;_->fib (n-1) + fib (n-2) juhtum n

See meetod on kompaktsem, kuid vähem visuaalne. Üldiselt:

juhtum<выражение0>/ (<образец1> -> <выражение1> ; <образец2> -> <выражение2> ; ... ; <образецN> -> <выражениеN> }

Funktsioonide määratlused

Haskellis võib samal funktsioonil olla mitu definitsiooni, mis eristuvad selle argumentide kirjutamisviisi järgi. Argumendikirjet nimetatakse mustriks ja funktsiooni hindamisel võrreldakse läbitud argumendid definitsioonide mustritega.

Vaatleme näiteks Fibonacci arvu arvutamise funktsiooni.

fib n = fib (n-1) + fib (n-2)

Funktsiooni väärtuse hindamisel võrreldakse selle üksikut argumenti selle definitsioonide mustritega ülalt alla. Kui argumendiks oli arv 2, siis esimene vaste ebaõnnestub ja teine ​​õnnestub ning selle tulemusena võtab funktsioon väärtuse 1. Kui argument ei vasta kahe esimese definitsiooni mustritele, siis see peab tingimata vastama argumendi nimele n (in sel juhul n võtab läbitud argumendi väärtuse) ja arvutatakse kahe eelmise Fibonacci arvu summa.

Kui funktsiooni definitsioonide hulgast sobivat ei leita, tekib tõrge ja programmi täitmine peatub.

Kaitseväljendid

Viimane võimalus alternatiivide valimiseks funktsioonide tulemuse arvutamisel on nn valveavaldised. Neid (erinevalt if-then-else ja case avaldistest) saab kasutada ainult funktsioonide määratlustes:

<имя функции> <список аргументов функции>

|<условие1> = <выражение1>

|<условие2> = <выражение2>

|<условиеN> = <выражениеN>

Kõik sümbolid “|” peavad algama oma realt ja ühe taandega. Funktsiooni väärtuse arvutamisel itereeritakse kõik tingimused, mis on Bool-tüüpi avaldised, ülalt alla. Millal see leitakse i-s tingimus, mille tulemus on Tõene, hinnatakse avaldist i, mille tulemus on funktsiooni tulemus.

Kirjutame näiteks Fibonacci numbri leidmise funktsiooni:

|muidu = fib (n-1) + fib (n-2)

Muidu on siin ilmselgelt tõene tingimus, mis on alati võrdne tõega.

5.5 Mustri sobitamine

Mustri sobitamine on mugav viis seostama mõne väärtuse erinevaid osi antud nimed(sümbolid) . Mustri sobitamist kasutatakse definitsioonides ja käändeavaldistes.

Funktsioonide määratlemisel toimub argumentides mustri sobitamine. Lihtsamal juhul, kui argumendid on määratud ainult nimedega, seostatakse argumendid täielikult nende nimedega. Näiteks:

f x = fst x + snd x

See funktsioon arvutab korteeži elementide summa. Standardfunktsioonid fst ja snd saavad vastavalt korteeži esimese ja teise elemendi.

*Main> f (2,4)

Mustri sobitamise abil saame selle funktsiooni argumendi sisu visuaalsemal viisil esile tõsta:

Selle funktsiooni (2,4) arvutamisel sobitatakse selle korteeži elemendid funktsiooni definitsioonis määratud mustriga, st sümbol "a" saab väärtuse 2 ja sümbol "b" väärtus 4.

Nii saame määratleda funktsioonide fst ja snd analoogid:

Pange tähele, et funktsiooni fst1 definitsioon ei kasuta x-i ja funktsiooni snd1 definitsioon ei kasuta y-d. Sellistel juhtudel, kui osa näidist (või kogu argumenti) ei kasutata, ei ole vaja selle osa (või argumendi) nime määrata - nime asemel piisab allkriipsu märgist “_ ”. Määratleme need funktsioonid uuesti:

Sellist kirjutamisviisi kasutades ei pea mustrite mittevajalikele osadele nimetusi välja mõtlema ning funktsiooni definitsiooni lugedes saab kohe selgeks, et seda argumendi osa ei kasutata.

Mustrid võivad määrata mis tahes konstruktoreid (eelmistes näidetes kasutati korteeži konstruktorit), näiteks loendi, korteeži või mis tahes kohandatud andmetüüpide konstruktoreid, aga ka konkreetseid argumentide väärtusi (nagu Fibonacci arvude näites). . Mõnel juhul võib vaste ebaõnnestuda ja proovitakse järgmist vastet (järgmisest definitsioonist või käändemustrist). Vaatame näidet:

f _ 0 = viga "nulliga jagamine"

f (a, b) c = (a+b)/c

Siin on funktsiooni f arvutamisel teine ​​argument 0, siis valitakse funktsiooni hindamiseks esimene definitsioon, vastasel juhul teine, kuna nimega võrdlemine õnnestub alati. Veafunktsioon peatab programmi täitmise antud tekst vead. Näited kirjeldatud funktsiooni kasutamisest:

*Põhi> f (1,2) 3

*Põhi> f (3.2) 1

*Põhi> f (5.5) 5

*Põhi> f (5,5) 0

*** Erand: nulliga jagamine

Lisaks saab iga mustri nimetada nii, et struktureeritud argumendile pääseb juurde elemendi kaupa või tervikuna. Tehtajat @ ​​kasutatakse mustri nimetamiseks. Tema ees läheb nimi, mille kaudu pääsete juurde argumendile tervikuna ja seejärel proovile endale. Näiteks,

func1 p@(a, b) = a + b + func2 p

func2(a, b) = a*b

5.6 Loendid

Nimekiri on üks kõige olulisemad struktuurid andmed, mis sisaldavad sama tüüpi elemente. Loend ei tohi sisaldada erinevat tüüpi elemente (erinevalt korteežidest). Loendi tüübi kirjeldamiseks kasutatakse nurksulgusid. Näiteks on see loogilist tüüpi elementide loendi tüübi kirjeldus.

Loendid luuakse loendikonstruktori abil - operatsiooniga ":". Loend võib olla tühi või koosneda peast (loendi esimene element) ja sabast (ülejäänud elementide loend). Tühja loendit tähistatakse tühjade nurksulgudega: "", ja loendist, mis koosneb peast x ja sabast y, kirjutatakse loendikonstruktori abil: "x:y".

Loendite kirjutamiseks on mugavam viis: loetledes elemendid nurksulgudes. Näiteks võib täisarvude loendi 1 kuni 3 kirjutada järgmiselt:

1: = 1:2: = 1:2:3:

Loendikonstruktorit saab kasutada mustrite sobitamisel. Selle sobitamise korral jagatakse argumendina edastatud loend peaks ja sabaks, millele pääseb juurde mustris määratud märkide kaudu. Kui loend on tühi, siis võrdlus sisse see määratlus jääb edutuks.

Näiteks saate kirjeldada funktsiooni loendi pea võtmiseks:

Peafunktsiooni ülaltoodud teostuses loendi saba ei kasutata, selle nime saate asendada allkriipsuga:

Sarnaselt võib kirjeldada ka nimekirja saba võtmise toimingut.

Need kaks funktsiooni (pea ja saba) annavad vea, kui need edastatakse neile tühja loendiga, kuna vaste ei õnnestu.

Vaatame veel ühte näidet loenditega töötamise funktsioonist: loendi pikkuse arvutamise funktsioon:

pikkus (_:t) = 1 + pikkus t

Kui sisestusloend on tühi, siis võrdluse tulemusel töötab esimene definitsioon, vastasel juhul töötab teine.

Haskelli stringid on märkide loendid. Märgid kirjutatakse apostroofidega (näiteks "c") ja stringid jutumärkides (näiteks "string"). Mis tahes stringi saab esitada standardne märge loendid, näiteks string "string" sarnaneb loendiga ["s","t","r","i","n","g"]. Stringe töödeldakse täpselt samamoodi nagu loendeid.

Loendi elementidele pääseb juurde järjestikku, lõigates järk-järgult ära pea ja saba. Näiteks loendi n-nda elemendi saamiseks (alates 0-st) võite kirjutada funktsiooni:

saadaN(x:_) 0 = x

getN (_:t) n = getN t (n-1)

Loendi n-nda elemendi võtmine on realiseeritud standardfunktsioonis "!!". Seda kasutatakse järgmiselt:

Siin on loend loend, n on otsitava elemendi number. Elementide nummerdamine algab nullist. Kui loendi pikkus on väiksem või võrdne otsitava elemendi arvuga, põhjustab selle funktsiooni arvutamine vea.

Samuti on võimalik määrata loendatavate elementide (nt täisarvud või märgid) loendid järgmiselt:

Siin on X1 aritmeetilise progressiooni algus ja X2 selle lõpp. Näiteks on loend. Sel viisil saate määrata ainult kasvavaid loendeid, mille samm on võrdne ühega. Kui teil on vaja määrata erineva sammuga jada, saate kasutada järgmist tähistust.

Selles versioonis on X1 jada esimene element, X2 on teine, X3 on võimalik viimane element. Jada samm on valitud kui X2-X1 ja jada sisaldab elemente, mis asuvad ainult X1 ja X3 vahel. Näiteks on loend.

Samuti on võimalus täpsustada loendeid olemasolevate põhjal. Üldiselt võib selle meetodi kirjutada järgmiselt:

[<выражение> | <образец> <- <список>, <ограничение1>, ..., <ограничениеN>]

Teatud loendist valitakse välja elemendid, mis vastavad mustrile ja vastavad kõigile piirangutele ning selle mustri abil avaldise poolt hinnatud elementidest koostatakse loend. Näiteks,

[ x^2 | x<- ,mod x 3 == 0]

See avaldis koostab 3-ga jaguvate paaritute arvude ruutude loendi ühest kuni 30-ni. Tulemuseks on:

Lisaks on loenditega töötamisel väga kasulik standardmoodulis defineeritud kaardifunktsioon. See rakendab funktsiooni antud loendile ja tagastab loendi, mis tuleneb selle funktsiooni rakendamisest algse loendi elementidele. Kaardifunktsiooni saab rakendada järgmiselt:

kaart:: (a -> b) -> [a] -> [b]

kaart f xs =

See tähendab, et esimese argumendina on funktsioon, mis teisendab a-tüüpi objektid b-tüüpi objektideks, ja teise argumendina a-tüüpi elementide loendi. Kaardifunktsiooni tulemuseks on b-tüüpi elementide loend.

Kaardifunktsiooni kasutamisel on mugav kasutada nimetuid funktsioone, mida kirjeldatakse λ-avaldiste kujul (täpsemalt on juttu kõrgemat järku funktsioonide osas). Näiteks arvude 1 kuni 10 ruutude loendit võib kirjeldada järgmiselt:

kaart (\x->x*x)

Selle funktsiooni tulemuseks on loend:

Kuna Haskellil on sisseehitatud astendamise operatsioon, see nimekiri saab nii:

kaart (^2)

Võite kasutada ka muid funktsioone, näiteks:

kaart inc

Vaatame mitmeid loenditega töötamiseks mõeldud standardmoodulis määratletud funktsioone.

Funktsiooni nimi

Kirjeldus

loendi pea (esimene element).

loendi saba (kõik peale esimese elemendi).

loendi viimane element

kõik loendi elemendid, välja arvatud viimane

[a] → Int → a

etteantud numbriga element

loendi pikkuse arvutamine

Int → [a] → [a]

võta loendist esimesed n elementi

Int → [a] → [a]

tühistada loendist esimesed n elementi

loendi vastupidine järjekord

(Arv a) => [a] → a

loendi elementide summa

(Arv a) => [a] → a

loendi elementide korrutis

[a] → [a] → [a]

loendi ühendamine

5.7 Kohalikud määratlused

Haskell on puhas funktsionaalne programmeerimiskeel, seetõttu ei saa sellel olla globaalseid muutujaid, tegelikult pole muutujaid üldse. Ühtegi eset ei saa muuta, vana kasutades saab ainult uue. Mõned muutujate "asendajad" võivad olla funktsiooniparameetrid ja kohalikud funktsioonid.

Kirjeldamiseks on kaks võimalust kohalikud funktsioonid: kasutades reserveeritud sõna kus Ja lase. Kohalike funktsioonide määratlemist kus saab kasutada ainult funktsiooni definitsioonis. Määratleme funktsiooni kolme arvu aritmeetilise keskmise arvutamiseks:

keskmine 3 x y z = s / 3

Üldiselt saab kus kasutamise kirjutada järgmiselt:

<имя функции> <аргументы> = <выражение>

<определение 1>

<определение 2>

<определение N>

Kõik määratlused peavad olema samas taandes, kuid suuremad kui rida, mis sisaldab kus. Soovi korral võite kasutada järgmist süntaksit:

<имя функции> <аргументы> = <выражение>kus (<определение 1> ; <определение 2> ; ... ; <определение N> }

Teist meetodit (kasutades let) saab kasutada mis tahes avaldises (mitte ainult funktsioonide määratlemisel). Erinevalt sellest, kus tuleb lokaalsete funktsioonide definitsioonid kirjutada enne avaldist ennast. Kirjutame eelmise näite ümber:

keskmine3 x y z =

Üldiselt näeb see meetod välja järgmine:

<определение 1>

<определение 2>

<определение N>

<выражение>

Kõik definitsioonid peavad olema samas taandes, kuid suuremad kui rida, mis sisaldab let. Sõna in peab olema taandega vähemalt laskma. Soovi korral võite kasutada järgmist süntaksit:

lase (<определение 1> ; <определение 2> ; ... ; <определение N>) sisse<выражение>

Sel juhul taanet eiratakse.

5.8 Interaktiivse keskkonna lisavõimalused

Lisaks funktsioonide või avaldiste hindamisele on GHCi interaktiivsel keskkonnal palju lisavõimalusi. Paljudele neist pääseb juurde käskude kaudu, mis algavad märgiga ":". Näiteks GHCi reale käsu “:?” kirjutamine näete kõigi teiste käskude loendit (automaatne täitmine töötab ka käskude puhul). Vaatame kõige kasulikumaid käske.

:t - määratud avaldise tüübi hankimine. Näiteks:

*Main> :t tagurpidi (saba "ema")

tagurpidi (saba "ema") ::

:i - funktsiooni kohta info hankimine (tüüp, millises moodulis või klassis see on defineeritud). Näiteks:

*Põhi> :ma vastupidi

tagurpidi:: [a] -> [a] -- Määratletud GHC-s. Nimekiri

:l - koormus määratud moodul ja muuta see aktuaalseks.

:m - määratud mooduli laadimine või mahalaadimine.

:q - sulgege GHCi.

Üks veel kasulik funktsioon on võime defineerida funktsioonide määratlusi otse GHCi-s. Selleks kasutatakse lihtsustatud let-konstruktsiooni. Näiteks:

*Main> olgu f x = x+2*x

Täielik let-konstruktsioon toimib ainult selle väljenduse piires:

*Põhi> olgu z = 10 in z+z

:1:0: Ei kuulu ulatusse: "z"

GHCi teatab sellest antud nimi praeguses ulatuses määratlemata.

5.9 Kõrgema järgu funktsioonid

Haskellis saab funktsioone kasutada nagu kõiki teisi objekte – neid saab salvestada loendites ja korteežides, edastada teistele funktsioonidele ja tagastada muude funktsioonide tulemusel.

Funktsioone, mis aktsepteerivad või tagastavad muid funktsioone, nimetatakse kõrgemat järku funktsioonideks. Üks selline funktsioon on näiteks kaardifunktsioon, mis kasutab antud funktsioon kõikidele loendi elementidele.

Täitke see osa korralikult

Mõelge kahe numbri liitmise funktsioonile:

pluss x y = x + y

Plussfunktsiooni saab kirjeldada erinevalt:

Tehe +, mis on sulgudes, on 2 muutuja funktsioon, seega, rakendades sellele kahte parameetrit, on tulemuseks nende summa. Kui rakendame sellele funktsioonile ainult ühte argumenti, näiteks arvu 3, saame ühe argumendiga funktsiooni, mis määrab selle argumendi lisamise arvule 3:

Seda funktsioonide järkjärgulist jagamist funktsiooniks, mis võtab ühe parameetri, ja funktsiooniks, mis tagastab teise funktsiooni, mis arvutab kõik muu, nimetatakse funktsiooni currying.

Teine võimalus plussfunktsiooni kirjeldamiseks:

Kasutades plussfunktsiooni (mis tahes ülaltoodud rakendus), saate kirjutada juurdekasvufunktsiooni:

ink x = pluss 1 x

Karri kasutades saate sama funktsiooni teise teostuse:

Selgub, et funktsioon inc tagastab funktsiooni, mis lisab ühele selle parameetritest 1.

Lisaks sellele funktsioonide kirjutamisviisile Haskellis on võimalus neid määrata ka λ-arvutuse abil. Näiteks plussfunktsiooni saab realiseerida järgmiselt:

pluss = \x y -> x+y

Siin tähendab “\” λ-avaldise algust, seejärel on loetletud parameetrid (x y) ja noole (->) järel on avaldis.

λ-arvutust saab kasutada nimeta funktsioonide defineerimiseks. Näiteks saate kasutada lisamisfunktsiooni ilma seda eraldi kirjeldamata:

See avaldis on funktsioon. Seega, rakendades sellele parameetreid, saame tulemuse.

(\x y -> x+y) 3 6

Tulemuseks on number 9.

Nimeta funktsioonid on kasulikud näiteks funktsioonide edastamisel parameetritena teistele funktsioonidele, kui te ei pea funktsiooni vastavalt vormindama.

Kui mõnda funktsiooni parameetrit selle väärtuse arvutamisel ei kasutata, võib selle nime ära jätta, asendades selle sümboliga “_”. Näiteks funktsioon, mis tagastab kahest parameetrist esimese, võib välja näha järgmine:

Või tavalises tähistuses:

firstoftwox_=x

5.10 Lõpmatud andmestruktuurid

Haskell on mitterange keel, see tähendab, et see kasutab nn laisaid arvutusi. Nagu eespool mainitud, tähendab see, et hinnatakse ainult neid avaldisi, mis on vajalikud funktsiooni tulemuse arvutamiseks. Vaatleme enda kaudu määratletud funktsiooni:

Ilmselgelt ei saa selle väärtust välja arvutada. Kui proovite seda teha, tekib silmus. Vaatame veel ühte funktsiooni:

Selle väärtus ei sõltu parameetrist x. Seetõttu pole vaja seda arvutada. Ja väljenduse hindamine

ei vii tsüklini ja tagastab tulemuse, mis on võrdne 1-ga.

Selline laisk hindamine võimaldab korraldada lõpmatuid andmestruktuure, näiteks lõpmatuid loendeid. Üks kõige enam lihtsaid viise lõputu loendi ülesanded on ülesanne aritmeetilised progressioonid. Selleks ei pea te intervalli lõppu määrama. Näiteks järgmised loendid on lõpmatud:

Neid täpsustavad ainult kaks esimest elementi, millest arvutatakse edenemise samm. Kui edenemise teist elementi ei täpsustata (nagu esimeses näites), siis eeldatakse, et samm on 1.

Nende loenditega on võimalik teha samu toiminguid ja kasutada samu funktsioone, mis tavaliste loendite puhul, mille puhul ei ole tulemuse arvutamiseks vaja kõiki loendi elemente. Sellised funktsioonid hõlmavad pea ja saba võtmist, loendi i-nda elemendi hankimise funktsioone jne.

Lõpmatud loendid on kasulikud mõne probleemi lahendamiseks. Näiteks saab faktoriaalset arvutamist rakendada järgmiselt:

fakt n = faktiloend !! n

faktiloend = fl 1 1

kus fl x n = x:(fl (x*n) (n+1))

Arvu n faktoriaali määramise funktsioon (fakt n) tagastab kui n-s tulemus Faktiloendi element, mis on kõigi naturaalarvude faktoriaalide lõputu loend. Selle loendi elemente hinnatakse ja need hõivavad mälu ainult siis, kui nende väärtusi on vaja.

Lõpmatud võivad olla mitte ainult loendid, vaid ka kõik muud programmeerija määratud andmestruktuurid, näiteks puud.

6 Andmetüübid ja moodulid

6.1 Kohandatud tüübid ja andmestruktuurid

Funktsioonitüüpide kirjeldamisel võib teil juba vaja minna kohandatud sünonüüme olemasolevad tüübid. Näiteks kui teatud tüübi määratlus on tülikas, on mugav seda ühe nimega nimetada. Pole eriti tore kirjutada iga kord midagi sellist nagu [(Täisarv)); mugavam on sellele tüübile anda nimi, näiteks MyType1. See määratlus näeb välja selline:

tüüp MyType1 = [(Täisarv,)]

See tüübimääratlus ei kirjelda uus struktuur andmeid, vaid annab ainult juba olemasolevate tüüpide kombinatsiooni nime.

Enda andmetüübi määramiseks kasutage järgmist tähistust.

andmeid<имя> = <значение1> | <значение2> | ... | <значениеN>

Seega andmestruktuur nn<имя>, mis võib võtta väärtuse 1, väärtuse 2 ja nii edasi kuni väärtuseni N. Need andmetüübi väärtused on andmekonstruktorid. Igal konstruktoril peab olema kordumatu nimi, mis algab suure tähega. Andmekonstruktori nimi võib olla sama mis andmetüübi nimi, näiteks andmetüübi “värv” kirjeldust saab esitada järgmiselt:

andmed Värv = Punane | Roheline | Sinine

Lisaks saab konstruktor teatud andmeid funktsioonina aktsepteerida. Näiteks saate värvide andmetüüpi laiendada, lisades punase, rohelise ja sinise kombinatsioonides värviesituse:

andmed Värv = Punane | Roheline | Sinine | RGB Double Double Double

See tähistus tähendab, et värvi tüüpi objektidel võivad olla väärtused Red, Green, Blue või RGB r g b, kus r g b - reaalarvud. Näiteks võite määratleda funktsiooni, mis tagastab kollase värvi:

Andmetüüpide määramine võib olla polümorfne, nagu ka funktsioonide määramine. See tähendab, et saate määrata andmetüübi, mis sisaldab mõnda muud, kuid mitte konkreetne tüüp. Näiteks võib standardtüüpi Maybe kirjeldada järgmiselt:

andmed Võib-olla a = Mitte midagi | Lihtsalt a

See tähendab, et tüüpi objekt (Võib-olla a) võib võtta väärtuse Nothing või Just x, kus x on mingit tüüpi a objekt. Sellistele objektidele juurdepääsuks kasutatakse mustri sobitamist. Näiteks saate rakendada funktsiooni unJust, mis tagastab konteineriklassi Maybe sisu, kui see ei võrdu väärtusega Nothing.

unjust (Just a) = a

Vaatame teist näidet:

leia:: (Võrrand a) => a -> [a] -> Võib-olla Int

find_=Mitte midagi

| x == a = Lihtsalt 0

| muidu = juhtu (leia xs) of

(Ainult i) -> Lihtsalt (i+1)

Mitte midagi -> mitte midagi

See funktsioon tagastab teise parameetrina edastatud loendi esimese parameetrina edastatud objekti esimese esinemise indeksi või mitte midagi, kui see puudub. Funktsiooni definitsioon kasutab klassi Eq, mis piirab funktsiooni parameetrite tüübid võrreldavate tüüpidega. Klassidest tuleb juttu järgmises peatükis.

Andmemärksõna saab kasutada mis tahes andmestruktuuri kirjeldamiseks. Kirjeldame binaarpuud teise näitena:

andmed Puu a = null | Sõlm a (Puu a) (Puu a)

See definitsioon ütleb, et a-tüüpi elementidega puu võib olla tühi puu või koosneda tüüpi a elemendist ja veel kahest puust, mis on defineeritud sarnaselt.

Kirjeldame elemendi lisamise funktsiooni binaarsesse otsingupuusse.

addtotree null x = sõlm x null null

addtotree (Sõlm y vasakul paremal) x

|x

|x>y = sõlm y vasakul (lisa puu paremal x)

|muidu = sõlm y vasak parem

Tühjale puule lisamisel saadakse ühe sõlmega puu, mis sisaldab lisatavat elementi. Ja kui puu pole tühi, siis sõltuvalt lisatud elemendi ja puu juure elemendi suhtest valitakse järgnev arvutusstrateegia. Kui lisatav element on väiksem, siis tuleb see lisada vasakpoolsesse alampuusse, mistõttu saadakse sama võtmega ja parema alampuuga puu, kuid uue vasakpoolse alampuuga, mis saadakse elemendi vanale lisamisel. Kui lisatud element on juurelemendist suurem, on tulemuseks muudetud parempoolse alampuuga puu. Kui lisatud element langeb kokku juurelemendiga, siis kattub tulemus argumendina edastatud puuga.

Korraldame puu kuvamise ekraanil äärtega vormis, määratledes funktsiooni selle (puu) stringiks teisendamiseks.

showtree tree = showtr tree 0 kus

showtr (Nul) n = replikatsioon n "\t" ++ "-\n"

showtr (Sõlm x vasak parem) n =

showtr paremale (n+1) ++

kordama n "\t" ++ näita x ++ "\n" ++

showtr vasakul (n+1)

Kohalik funktsioon showtr võtab kaks argumenti – puu ja sügavuse tase. Kasutatav näitamisfunktsioon on standardfunktsioon peaaegu kõigi Haskelli objektide teisendamiseks stringivormiks.

Nüüd avaldise arvutamise tulemusena

addtotree (addtotree (addtotree (addtotree

(addtotree (addtotree (addtotree Nil

mis tähendab täisarvude 5, 3, 8, 1, 4, 6, 9 järjestikust lisamist puule, saame mingi puu tüüpi objekti. Edastades selle meie showtree funktsioonile, saame selle puu stringi esituse.

Kasutajaandmete määratlemiseks on veel üks viis - märksõna uustüüp. Selle kasutamine on peaaegu sama, mis andmetel, välja arvatud üks erand: newtype'iga kirjeldatud andmetüüpidel on ainult üks andmekonstruktor täpselt ühe väljaga. Seda meetodit kasutatakse uue andmetüübi loomiseks olemasoleva põhjal:

newtype MyInt = MyInt Int

Lisaks on võimalik määratleda andmestruktuure, näiteks nimeliste väljadega kirjeid. Tavaliste korduste või andmestruktuuride kasutamine objektide kasutamisel koos suur summa erinevad omadused ei ole alati mugav. Peate meeles pidama nende ilmumise järjekorda ja navigeerima ainult selle järgi või looma iga atribuudi jaoks vastava funktsiooni. Nimetatud väljadega kirje võimaldab seda protsessi automatiseerida ja annab nimede kaudu juurdepääsu selle elementidele, mis lihtsustab oluliselt tööd. Neid kirjeldatakse järgmiselt:

<имя записи> {

<имя поля 1> :: <тип поля 1>,

<имя поля 2> :: <тип поля 2>,

<имя поля N> :: <тип поля N>}

Näiteks:

andmed Inimene = Inimene (

kõrgus::topelt,

Objekti täpsustamiseks seda tüüpi võib kirjutada:

mike = inimene (nimi = "Mike", pikkus = 173,4, kaal = 81,3)

Sellise objekti muutmiseks või pigem uue hankimiseks, kuid mõne välja muudetud korral, võite kirjutada järgmiselt:

john = mike (nimi = "John")

6.2 Moodulid

Haskell toetab modulaarne programmeerimine st programmi saab jagada mooduliteks ja iga moodulit saab kasutada mitmes programmis.

Iga moodul peaks välja nägema selline:

Kui valikuline osa on välja jäetud, ei saa seda moodulit teistest moodulitest kasutada. Mooduli nimi, nagu ka andmetüübid, peab algama suure tähega ja ühtima ka failinimega.

Väliste moodulite definitsioonidele (funktsioonidele, andmetüüpidele, klassidele) juurdepääsu saamiseks peate neid kirjeldama programmi alguses järgmiselt:

importida<модуль1>

importida<модульN>

<остальной код>

Pärast seda muutuvad kõik nende moodulite definitsioonid kättesaadavaks praeguses moodulis. Kui aga praegune moodul määratleb funktsioonid või andmetüübid, millel on sama nimi kui imporditavatel, või kui erinevad pistikprogrammi moodulid sisaldavad sama nimega funktsioone või andmetüüpe, ilmneb tõrge. Selle vältimiseks on vaja keelata selliste definitsioonide kasutamine vastavatest moodulitest. Seda tehakse järgmiselt.

importida<модуль>peidus (<скрываемые определения>)

Lisaks on imporditud mooduli enda kirjeldamisel võimalik kontrollida juurdepääsu definitsioonidele. Selleks peate loetlema saadaolevad määratlused mooduli nime järel sulgudes:

moodul<Имя>(<определения>) kus<код>

Kõigist kirjeldustest välised moodulid Saadaval on ainult need, mis on loetletud sulgudes.

Näitena võime tuua 3 moodulist koosneva programmi:

-- Prog. hs

moodul Prog kus

import Mod2 peitmine (modfun)

moodul Mod1(modfun) kus

modfun = viis * 2

moodul Mod2 kus

Moodulis Mod1 määratletud funktsioon modfun on saadaval moodulis Prog, kuid funktsioon viis pole saadaval.

7 Klassid ja monaadid

7.1 Klassid

Haskell toetab objektorienteeritud paradigmat. Kuid see erineb veidi tavapärasest teistes keeltes kasutatavast. Klassi eksemplar on andmestruktuur. Iga klass hõlmab teatud funktsioonide kirjeldust, mis on seotud selle eksemplarideks olevate andmetüüpidega töötamiseks klass.

Klassi määratlemiseks kasutatakse järgmist tähistust:

klass [(<ограничения>) =>] <имя> <переменная типов>kus<функции>

Tüübimuutuja nimetab tüübi, mis peab olema selle klassi eksemplar. Sõna järel kus on funktsioonide tüüpide kirjeldused, samuti mõne sellise funktsiooni väljendamine üksteise kaudu. Neid avaldisi kasutades saab tõlk ise määrata osa klassieksemplari funktsioonidest, kui on antud mõni muu osa. Piirangud on määratlus, et meie klassi eksemplar peab olema ka siin loetletud klasside eksemplar. See on omamoodi pärand. Vaatame näidet:

klass Eq a kus

(==), (/=) :: a -> a -> Bool

x == y = mitte (x/=y)

x /= y = mitte (x==y)

Klass Eq on võrreldavate tüüpide klass. Selle klassi iga eksemplari jaoks tuleb määratleda võrdsus- ja ebavõrdsusfunktsioonid. Teine rida tähendab, et funktsioonid (==) ja (/=) peavad võtma kaks a-tüüpi argumenti ja tagastama Bool-tüüpi objekti, st tõene või väär.

Klassi definitsiooni järgmised read ütlevad, et kui funktsioon (/=) on defineeritud, siis funktsioon (==) defineeritakse selle kaudu vastavalt ja vastupidi. Tänu sellele peab programmeerija defineerima vaid võrdsuse (või ebavõrdsuse) võrdlusfunktsiooni ja tõlk defineerib teise funktsiooni ise.

Veel üks näide klassi määratlusest, kuid pärilikkusega:

klass Eq a => Minuklass a kus

myFunc:: [a] -> a -> Int

Kui klass on määratletud, saate deklareerida mis tahes andmetüübi selle klassi eksemplariks:

näiteks MyClass Double kus

|x==z = 1 + myFunc xs z

|muidu = myFunc xs z

Seega kuulutame standardtüübi Double meie klassi MyClass eksemplariks ja defineerime funktsiooni myFunc funktsioonina, mis arvutab elementide arvu esimeses argumendis, mis on võrdne funktsiooni teise argumendiga.

Määrates tüübi klassi eksemplarina, saate selles kirjeldatud funktsioone rakendada seda tüüpi objektidele.

test = myFunc x 2 kus

Polümorfsete parameetritega funktsioonide kirjeldamiseks kasutatakse sageli klasse. Näiteks kui on vaja kirjeldada funktsiooni, mis kasutab paljude andmetüüpide puhul võrdlusoperatsiooni, siis tuleb määrata, et selle võrdluses osalevad parameetrid peavad olema klassitüüpi Eq, mis tagab vastava funktsiooni realiseerimise.

Kui programmeerija kirjeldab oma andmestruktuuri, siis see ei kuulu ühtegi klassi. Vajadusel peab programmeerija rakendama oma struktuurile vastavad funktsioonid ja näitama selle klassikuuluvust. Näiteks saab eelnevalt kirjeldatud Tree andmestruktuuri kuulutada klassi Näita eksemplariks, nii et tõlk saab seda ekraanil kuvada ilma showtree funktsiooni käsitsi kutsumiseta. Selleks kirjutame:

näide Näita a => Näita (Puu a) kus

Pärast seda, kui interpretaator saab Tree-tüüpi tulemuse, saab ta seda ekraanile kuvada ja mis tahes muu funktsioon saab teisendada puu tüüpi objekti stringivorminguks, kasutades funktsiooni show.

Lisaks on veel üks viis tüübi klassi kuuluvuse deklareerimiseks. See on lihtsam, kuid ei suuda alati programmeerija vajadusi rahuldada. See koosneb vajalike klasside loetlemisest kohe struktuuri deklareerimisel:

andmeid<имя> = <значение1> | <значение2> | ... | <значениеN>tuletamine (<класс1>,<класс2>,...,<классM>)

Siin vajalikud funktsioonid kuvatakse võimalusel automaatselt. Näiteks määratleme oma puutüübi Eq klassi eksemplaridele, et puid saaks omavahel võrrelda.

andmed Puu a = null | Puu a (Puu a) (Puu a)

Nüüd on võimalik puid võrrelda toimingu “==” abil ja võimaldab neid kasutada funktsioonides, mis seda nõuavad.

7.2 I/O

Võib-olla kirjutage monaadidest lähemalt

Nagu eespool öeldud, on Haskell puhas funktsionaalne programmeerimiskeel, st selles sisalduvatel funktsioonidel ei saa olla kõrvalmõjusid ja funktsioonide hindamise järjekord pole määratletud. Kuid mõnel juhul ei saa seda vältida. Sellised juhtumid hõlmavad töötamist kasutajaga, failidega, andmebaasidega jne. Haskelli keel näeb ette selliste funktsioonide määramise monaadide – spetsiaalsete konteineriklasside – abil. Haskelli õppimisel peetakse monaade kõige keerulisemaks, seega alustame kohe näidetega. Mõnede asjade selgitused jäetakse ära, et lugejat mitte segadusse ajada. Mõelge järgmisele näitele:

putStrLn "Tere maailm!"

putStrLn "Hüvasti maailm!"

Selles näites on põhifunktsioonil kõrvalmõjud - selle hindamise tulemusena kuvatakse ekraanile tekst, mis määrab hindamise järjekorra - kõigepealt kuvatakse ekraanil esimene rida, seejärel teine.

Do notatsiooni kasutamisel taandub "ebapuhaste" funktsioonide programmeerimine monaadide abil peaaegu tuttavaks kohustuslikuks programmeerimiseks.

Iga järgmine rida peab olema eritüübi – monaaditüübi – funktsioon. I/O-ga töötamise korral on see tüüp (IO a).

Töötamiseks käsurida Kasutatakse järgmisi funktsioone:

putChar::Char -> IOcharacter väljund

putStr::String -> IOväljundstring

putStrLn::String -> IOväljastab stringi uue reaga

IO a on monaadiline I/O tüüp, mis peidab enda sees kõrvalmõjusid. Tüüp IO String tähendab, et funktsioon tagastab stringi sisaldava tulemuse ja IO() tähendab, et funktsioon tagastab tulemuse, mis ei sisalda midagi, selle kutsumise mõte on ainult kõrvalmõjude jaoks (näiteks väljund). Näide:

putStrLn "Mis su nimi on?"

nimi<- getLine

Siin tulebki sisse "eraldamine". Analoogiliselt imperatiivkeeltega võime öelda, et reas

nimi<- getLine

funktsiooni getLine tulemus määrati nimemuutujale. Aga nagu me teame, ei ole Haskellis muutujaid ja seega ka omistamisi.Sel juhul loodi mingi nimega objekt, mille väärtus võrdub funktsiooni getLine arvutamise tulemusega.Ehk kui pärast seda sa kirjutad jälle

nimi<- getLine

siis luuakse uus objekt, mille nimi kattub eelmisega.

Nii ekstraheeritakse monaadi funktsioonide tulemused. Sel viisil "muutujate" defineerimiseks tavaliste funktsioonide väärtustega kasutatakse lihtsustatud let-tähistust:

las nimi = "John"

Arvutusprotsessi hargnemine toimub sama, kui siis muu ja juhtumiga:

putStrLn "Mis su nimi on?"

nimi<- getLine

"GHC" -> putStrLn "Ei! Ma olen GHC!"

_ -> putStrLn("Tere, "++nimi++"!")

Kui haru peab koosnema mitmest funktsioonist, siis kasutatakse märksõna do:

putStrLn "Mis su nimi on?"

nimi<- getLine

putStrLn "Ei! Ma olen GHC!"

_ -> putStrLn("Tere, "++nimi++"!")

Silmused realiseeritakse rekursiooni abil, nagu ülal näidatud, või spetsiaalsete funktsioonide abil (seda rakendatakse ka rekursiivselt). Need funktsioonid hõlmavad funktsiooni mapM_. Selle tööpõhimõte on sarnane loendite kaardifunktsiooniga – see rakendab kõikidele loendi elementidele monaadifunktsiooni ja täidab neid järjestikku. Näide:

writeDigit x = tee

putStr "Numbreid:"

mapM_writeDigit

8 Näited

Tooge erinevaid näiteid (otsingupuud, AVL-puu, midagi muud)

Järeldus

Järeldus

Kasutatud allikate loetelu

1 http://ru. vikiraamatud. org/wiki/Funktsionaalse programmeerimise alused

2 http://www. *****/artikkel/funcprog/fp. xml

3 Duškini programmeerimine Haskellis – DMK Press, 2007 – 608 lk.

4 http://et. vikiraamatud. org/wiki/Haskell/Pattern_matching

5 http://www. haskell. org/tutorial/




Imperatiivse programmeerimise teoreetilised alused panid 20. sajandi 30. aastatel paika Alan Turing ja John von Neumann. Funktsionaalse lähenemise aluseks olev teooria kujunes välja 20.–30. Funktsionaalse programmeerimise matemaatiliste aluste arendajate hulgas on Moses Schönfinkel (Saksamaa ja Venemaa) ja Haskell Curry (Inglismaa), aga ka Alonzo Church (USA). Schönfinkel ja Curry panid aluse kombinatoorsele loogikale ning Church on lambdaarvutuse looja.

Funktsionaalne programmeerimine põhineb kombinatoorse loogika ja lambda-arvutuse ideedel.

Kuid teooria jäi teooriaks, kuni eelmise sajandi 50ndate alguses töötas John McCarthy välja Lisp keele (1958), millest sai esimene peaaegu funktsionaalne programmeerimiskeel. Aastaid polnud Lispil konkurente. Hiljem ilmusid funktsionaalsed programmeerimiskeeled APL (1964), ISWIM (1966) ja FP (1977), mida nii laialdaselt ei kasutatud.

Aja jooksul ei rahuldanud Lisp enam mõningaid tarkvaraarendajate vajadusi, eriti kuna programmikoodi maht ja keerukus kasvas. Selle asjaoluga seoses hakkas tüpiseerimine mängima üha olulisemat rolli. 1970ndate lõpus ja 1980ndate alguses töötati intensiivselt välja funktsionaalsete keelte jaoks sobivaid tippimismudeleid.

Enamik neist mudelitest sisaldas toetust võimsatele mehhanismidele, nagu andmete abstraktsioon ja polümorfism. Ilmunud on palju trükitud funktsionaalseid keeli: ML, Scheme, Hope, Miranda, Clean ja paljud teised. Lisaks kasvas pidevalt ka murrete arv.

ML (1973) – esimene keel Hindley–Milneri trükkimisega;
Scheme (1975) on üks kahest populaarseimast lispi keele murdest;
SASL, KRC, Miranda (1972–1985) on ühed esimesed laisad keeled;
Hope (1980) – üks esimesi algebraliste andmetüüpidega keeli.


Haskell Curry

Tulemuseks oli see, et peaaegu iga funktsionaalne programmeerimisrühm kasutas oma keelt. See takistas nende keelte edasist levikut ja tekitas palju probleeme.

Haskelli ajalugu algab 1987. aastal. Üksteise järel ilmusid uued funktsionaalsed programmeerimiskeeled. Pärast Miranda (Research Software Ltd, 1985) ilmumist kasvas huvi laiska arvutitöö vastu: 1987. aastaks oli esile kerkinud üle tosina puhtfunktsionaalse programmeerimiskeele.

Miranda oli kõige laialdasemalt kasutatav, kuid see oli patenteeritud tarkvara. 1987. aastal Oregonis Portlandis toimunud funktsionaalsete programmeerimiskeelte ja arvutiarhitektuuri (FPCA) konverentsil leppisid osalejad kokku, et selliste keelte jaoks avatud standardi määratlemiseks tuleks luua komitee. Komitee eesmärk oli ühendada olemasolevad funktsionaalsed keeled üheks ühiseks keeleks, mis annaks aluse edaspidistele funktsionaalsete programmeerimiskeelte arendamise uuringutele.

Nii sündis Haskell. See sai nime ühe kombinatoorse loogika rajaja Haskell Curry järgi.

1980. aastate lõpuks oli loodud palju funktsionaalseid keeli. Mõned neist avaldasid Haskellile märkimisväärset mõju:

Uuest keelest pidi saama vaba keel, mis sobib uurimiseks ja praktiliste probleemide lahendamiseks. Tasuta keeled põhinevad standardil, mille on koostanud arendajate komitee. Siis saab igaüks standardi juurutada ja keelekompilaatori kirjutada. Haskelli standardi esimene versioon avaldati 1. aprillil 1990. aastal.

Haskell 1,0 - 1,4

Haskelli esimene versioon (Haskell 1.0) ilmus 1990. aastal. Komitee katsete tulemuseks oli rida keelerakendusi (1.0, 1.1, 1.2, 1.3, 1.4).

Haskell 98

1997. aasta lõpus tuli edasise arengu aluseks Haskell 98 versioonid määratleda üheks stabiilseks, minimaalseks ja kaasaskantavaks keeleversiooniks ning sellega kaasnevaks standardteegiks õppimiseks. Komitee tervitas Haskell 98 laienduste ja variatsioonide loomist eksperimentaalsete funktsioonide lisamise ja rakendamisega.

1999. aasta veebruaris avaldati Haskell 98 keelestandard esmakordselt kui "The Haskell 98 Report". Jaanuaris 2003 avaldati parandatud versioon nimega Haskell 98 Language and Libraries: The Revised Report. Keel on jätkanud kiiret arengut, kusjuures Glasgow Haskell Compiler (GHC) teostus esindab keele de facto standardit.

Haskell 2010

Kaasaegne Haskelli standard Haskell 2010 kuulutati välja 24. novembril 2009; GHC toetab seda alates versioonist 7.0.1.

Võrreldes Haskell "98-ga, sisaldas see järgmisi muudatusi:

Kas ja kui siis veel
Hierarhilised moodulid
Tühjad muutujate deklaratsioonid
Stabiilsuslahendus
Kolmanda osapoole funktsioonide liides
Lineaarne kommentaari süntaks
Eestkostja mustrid
Kergekaaluline sõltuvusanalüüs
Keelejuhised (pragma)
N+k mustri puudumine

Andmetüübi konteksti puudumine
Muutujate täpploendid

Haskell areneb tänapäeval edasi. Kuid stabiilsed versioonid põhinevad vastavalt 1998. ja 2010. aasta standarditel. Kuid peale nende sisaldab Haskell palju laiendusi ja uusi ideid tuleb pidevalt juurde. Keelega tegelevad mitmed riigid üle maailma – Inglismaa, Holland, Ameerika ja Austraalia. Huvi Haskelli vastu on tingitud mitme protsessoriga tehnoloogiate populaarsusest. Haskelli mudel sobib hästi paralleelarvutuseks.

Haskelli loojalt

Curry on üldotstarbeline manustatud programmeerimiskeel, mida rakendatakse Haskelli peal. Curry keel ühendab sujuvalt funktsionaalse programmeerimise (pesastatud avaldised, kõrgema järgu funktsioonid, laisk hindamine), loogilise programmeerimise (tõvemuutujad, osalised andmestruktuurid, sisseehitatud otsingusüsteem) ja paralleelsüsteemide programmeerimismeetodid (paralleelavaldiste hindamine sünkroonimine Boole'i ​​muutujatega ).

Lisaks pakub Curry keel puhaste programmeerimiskeeltega võrreldes täiendavaid mehhanisme (võrreldes funktsionaalsete keeltega - mittetäielike andmete otsimine ja arvutamine, võrreldes loogiliste keeltega - tõhusam arvutusmehhanism tänu determinismile ja funktsioonide väljakutsumine vastavalt vajadusele ).

Populaarsus

Githubi veebisaidil on see programmeerimiskeelte populaarsuselt praegu 23. kohal.

Mopside tõlk/kompilaator Perl 6 jaoks.

Lava riistvara kirjeldamise keel.

Loomuliku keele töötlemise süsteem LOLITA.

Teoreemide tõestamise süsteemid Equinox/Paradox ja Agda.

Facebook

Rämpsposti filtreerimine on üks tähtsamaid ülesandeid, mida Facebooki insenerid lahendavad. Suurim suhtlusvõrgustik töötleb enam kui 1,5 miljardi inimese sõnumeid, nii et saate aru probleemi ulatusest. 2015. aastal võttis ettevõte kasutusele uued rämpspostivastased filtrid, mille väljatöötamisel kasutati Haskelli programmeerimiskeelt.

Vaatamata oma noorusele, eksperimentaalsele staatusele ja suhteliselt madalale populaarsusele valis Facebook Haskelli olulise mooduli loomiseks. Üks inseneridest, Louis Brandy, kes on osa uue rämpspostivastase filtri väljatöötamise meeskonnast, veetis selle projekti juures koos kolleegidega kaks aastat. Wiredile antud intervjuus selgitas ta, mis neid ajendas.

Louis Brandi, valides hoolikalt sõnu, nimetas Haskelli ideaalseks Facebooki rämpspostifiltri rakendamiseks, kuna see saab paralleelsete ülesannetega hästi hakkama ja võimaldab arendajatel neid ülesandeid hõlpsalt ja mugavalt käigu pealt seadistada. Facebook on nii suur projekt ja rämpspostisaatjad muudavad taktikat nii kiiresti, et koheselt jõustuvate rämpspostifiltrite arendamiseks ja pidevaks muutmiseks on vaja tööriista.

Kui vaadata kaasaegse Interneti arengut, siis paljud Interneti-projektid, mille jaoks on oluline mastaapsus ja reaalajas reageerimine, peaksid seda teed järgima. Facebooki arendajate sõnul on Haskelli keelel kõik võimalused saada laialdaselt populaarseks. Ainus takistus on asjaolu, et Haskell on teistest keeltest üsna erinev - ja see muudab massilise rände sinna raskeks.

Kuid tööstus liigub selgelt õiges suunas, mida tõendavad uued paralleelsed programmeerimiskeeled, nagu Google'i Go ja Mozilla Rust. Need ei pruugi olla nii tõhusad kui Haskell, kuid neid on lihtsam õppida. Igal juhul võib Haskelli tänada teiste programmeerimiskeelte arendamise tõuke ja uute paljutõotavate projektide käivitamisele kaasaaitamise eest.

Jevgeni Kozlov rääkis oma ajaveebis Haskelliga töötamise muljetest:

Puudused
Esiteks on keelde sisenemisel kõrge barjäär. Jah, Haskell on ilus, puhas ja sisutihe, kuid seda ei saavutata tasuta, vaid aju pika ümberstruktureerimise ja keeruliste abstraktsioonide, nagu monaadid, monaaditrafod, läätsed ja masinad, uurimisega.

Teiseks on raske produktiivset koodi kirjutada. Kuna Haskellis on kogu kood laisk, võib kergesti jõuda olukorrani, kus mällu on kogunenud gigabaite ootel arvutusi, kuid neid ei arvutata, sest neid pole veel küsitud.

Positiivsed küljed
Esiteks muidugi keele puhtus. Puhtus mõlemas mõttes: funktsioonide puhtus ja OOP paradigma täielik puudumine. See on väga lahe, et saate vaadata funktsiooni signatuuri ja aru saada, kas see võib põhjustada kõrvaltoimeid või mitte. OOP puudumine tähendab, et pole võimalik teha hirmutavaid asju, näiteks kontrollimata ülekandeid baasklassist alamklassi.

Kui kood on kirjutatud, on seda raske kahel viisil tõlgendada. Näiteks ei saa te funktsioonirakendust segi ajada funktsiooni viitega, nagu Scalas, kuna funktsioonid on Haskellis esmaklassilised objektid. Või näiteks ei saa te funktsiooni segamini ajada tüübi või klassiga, kuna kõik funktsioonid peavad algama väiketähega ja tüübid/klassid peavad algama suure tähega.

Keeleomadus, ilma milleta oleks Haskelist rääkimine mõttetu, on polümorfism. Vaevalt oleks liialdus, kui ütlen, et Haskell on keel, kus on maksimaalselt taaskasutatud koodi. Kõik funktsionaalsused, mis on isegi mõnevõrra korduvad, viiakse üle abstraktsioonile.

Kuigi see ei vasta ühele teie suurest kriteeriumist (staatiline* sisend), proovin ma Pythoni puhul seda teha. Siin on mõned põhjused, miks arvan, et peaksite seda vaatama:

  • Imperatiivse keele jaoks on see üllatavalt funktsionaalne. See oli üks asi, mis mind sellest teada saades rabas. Näiteks vaadake loendeid. Sellel on lambdad, esmaklassilised funktsioonid ja palju funktsionaalselt inspireeritud kompositsioone iteraatoritel (kaardid, voldid, tõmblukud...). See annab teile võimaluse valida probleemile kõige sobivam paradigma.
  • IMHO, nagu Haskell, on ilus kodeerida. Süntaks on lihtne ja elegantne.
  • Sellel on kultuur, mis keskendub asjade otsesele tegemisele, selle asemel, et keskenduda liiga täpselt tõhususele.

Saan aru, kui otsite midagi muud. Näiteks loogikaprogrammeerimine, nagu ka teised, võib teie jaoks sobida.

*Eeldan, et mõtlete siin staatilist tippimist, kuna soovite tüübid deklareerida. Tehniliselt Python - see on tugevasti trükitud keel, sest te ei saa suvaliselt tõlgendada näiteks stringi arvuna. Huvitaval kombel on Pythoni tuletised, mis võimaldavad staatilist tippimist, näiteks Boo.

2018-12-04T00:00Z

Kui soovite tugevat isiklikku proloogi, on Mercury huvitav valik. Olen seda varem teinud ja mulle meeldis erinev vaatenurk, mille see mulle andis. Sellel on ka tüübisüsteemis moded-ness (millised parameetrid peaksid olema vabad/fikseeritud) ja determinism (palju tulemusi on?).

Clean on väga sarnane Haskelliga, kuid sellel on ainulaadne tippimine, mida kasutatakse alternatiivina monaadidele (täpsemalt IO-monaadile). Sisendi unikaalsus muudab huvitavaks ka massiividega töötamise.

2018-12-11T00:00Z

Kuna te pole määratlenud muid piiranguid peale oma subjektiivsete huvide ja rõhutate "õppimise tasu" (okei, okei, ma ignoreerin staatilist tippimise piirangut), soovitan õppida mitut erinevat paradigma keelt ja keelt. eelistatavalt neid, mis on igaühe jaoks "eeskujulikud".

  • Lipsky dialekt koodi-andmete-andmete/homokonilisuse jaoks ja kuna need on head, kui mitte parimad, näited dünaamiliste (enam-vähem rangete) funktsionaalsete programmeerimiskeelte kohta
  • Proloog domineeriva loogilise programmeerimiskeelena
  • Smalltalk ainsa tõelise OOP-keelena (huvitav ka oma tavaliselt äärmiselt pildikeskse lähenemise tõttu)
  • Võib olla, Erlang või Clojure kui olete huvitatud paralleel-/paralleel-/jaotatud programmeerimiseks sepistatud keeltest
  • Edasi stack-orienteeritud programmeerimiseks
  • (Haskell rangeks funktsionaalseks staatiliselt tipitud laiskaks programmeerimiseks)

Eriti Lisps (CL mitte nii palju kui Scheme) ja Prolog (ja Haskell) katavad rekursiooni.

Kuigi ma ei ole nende keelte guru, olen veetnud mõnda aega nendega, välja arvatud Erlang ja Forth, ning nad kõik on andnud mulle silmiavavaid ja huvitavaid õppimiskogemusi, kuna igaüks läheneb probleemidele erineva nurga alt. .

Ehkki võib tunduda, et ma eirasin seda osa, et mul pole aega mitme keele proovimiseks, arvan pigem, et ühegi keelega veedetud aeg ei lähe raisku ja peaksite neid kõiki vaatama.

2018-12-18T00:00Z

Mis puudutab teie erialale sobivat, näib ilmselge valik olevat loogiline keel, nagu Prolog või selle tuletised. Loogilist programmeerimist saab teha väga korralikult funktsionaalses keeles (vt nt The Reasoned Schemer), kuid teile võib meeldida töötada otse loogikaparadigmaga.

Interaktiivne teoreetiline tõestussüsteem, nagu twelf või coq, võib samuti teie kujutlusvõimet äratada.

2018-12-25T00:00Z

Soovin täiendada oma teadmisi programmeerimises. (...) Mõtlesin, et esitan siinkohal küsimuse, samuti mõned punktid selle kohta, millist keelt ma otsin. Mõned neist on subjektiivsed, mõned on mõeldud Haskellist ülemineku hõlbustamiseks.

Tugev tüüpi süsteem. (...) See teeb ka mitteametliku arutlemise minu programmi õigsuse üle lihtsamaks. Olen mures korrektsuse, mitte tõhususe pärast.

Rõhk pigem rekursioonil kui iteratsioonil. (...)

Ma kardan, et saate siin ülemineku pisut lihtsamaks teha. Haskelli iseloomustab väga range tüübisüsteem ja puhtfunktsionaalne stiil ning peaaegu kõik, mis meenutab tavalist programmeerimiskeelt, nõuab vähemalt ühes neist kompromisse. Seda silmas pidades on siin mõned üldised ettepanekud, mille eesmärk on säilitada suurem osa sellest, mis sulle Haskelli juures meeldib, kuid mõne olulise nihkega.

    Ignoreeri praktilisust ja vali "rohkem Haskell kui Haskell": Haskelli tüüpi süsteem on lõpetamise ja muude segaste kompromisside tõttu auke täis. Puhastage segadus ja lisage võimsamaid funktsioone ning saate selliseid keeli nagu Coq Ja Agda, kus funktsiooni tüüp sisaldab tõestust selle õigsuse kohta (loogilise implikatsioonina saab lugeda isegi noolt -> funktsiooni!). Neid keeli kasutati matemaatiliste tõestuste ja programmide jaoks, millel on äärmiselt kõrged nõuded korrektsuse poole. Coq on ilmselt kõige rohkem silmapaistev keel stiilis, kuid Agda on rohkem Haskelli hõnguga (ja on ka Haskelli keeles kirjutatud).

    Ärge arvestage tüüpidega, lisage rohkem maagiat: kui Haskell on nõidus, Lisp on loomise toores, ürgne maagia. Lispi perekonna keeled (sh Skeem ja Clojure) omavad peaaegu enneolematut paindlikkust koos äärmise minimalismiga. Keeled on sisuliselt süntaksivabad, kirjutades koodi otse puu andmestruktuurina; Metaprogrammeerimine Lisp'is on mõnes keeles lihtsam kui mittemetaprogrammeerimine.

    Tehke veidi kompromisse ja liikuge peavoolule lähemale: Haskell kuulub laia keelte perekonda, mida ML on tugevalt mõjutanud ja millest võite tõenäoliselt ilma suuremate raskusteta üle minna. Haskell on tüüpide korrektsuse garantiide ja funktsionaalse stiili kasutamise osas üks rangemaid, kus teised on sageli kas hübriidstiilid ja/või teevad pragmaatilisi kompromisse. erinevatel põhjustel. Kui soovite end paljastada OOP-iga ja pääseda juurde erinevatele suurematele tehnoloogiaplatvormidele Scala JVM-is või F#.NET-il on Haskelliga palju ühist, pakkudes hõlpsat ühilduvust Java platvormid ja .NET. F# toetab otse Microsoft, kuid sellel on Haskelliga võrreldes mõned tüütud piirangud ja kaasaskantavusprobleemid mitte-Windowsi platvormidel. Scalal on otsesed analoogid Haskelli tüüpi süsteemile ja Java platvormideülesele potentsiaalile, kuid sellel on raskem süntaks ja sellel puudub tugev kolmanda osapoole tugi, mida F# naudib.

Refactoring, TDD, veajälgijad ja isegi (lõpuks!) minu armastatud Haskelli kohta. Kahjuks ei käsitletud piisavalt teemat “mis selles sinu Haskellis nii head on”. Selline tunne on nagu enamik IT-inimesi tõesti ei mõista funktsionaalse programmeerimise eeliseid. Selles märkuses püüdsin üksikasjalikult kirjeldada, miks mulle isiklikult Haskell meeldib.

Nagu ikka, ei sea ma endale eesmärgiks kedagi milleski veenda ega midagi tõestada. Lisaks ei pretendeeri ma tõe teadmisele ja võin mõnes punktis eksida. See, kas nõustute kirjutatuga või mitte, on teie enda otsustada. Kuigi esemed on nummerdatud, ei sorteerita neid kuidagi. Teisisõnu, see ei ole nii, et esimene punkt on tähtsam kui viies või midagi sellist.

1. Keele ilu ja deklaratiivsus

Kuna tegemist on subjektiivse asjaga ja millel pole ranget määratlust, on "programmeerimiskeele ilu" suurepärane teema holivaride jaoks. Seetõttu ma ütlen seda - mina isiklikult Haskell tundub olevat väga ilus keel. Mõned inimesed arvavad, et Haskell on raske, kuid see pole tegelikult nii. Üks põhjus, miks mulle Haskell meeldib, on keele lihtne ja loogiline tuum.

Kurb ja õnnetu Java programmeerija, kes on sunnitud kirjutama igasuguseid privaatseid staatiline finaal mööduv lenduv Nimekiri > dramaatiliseltTerribleList = uus ArrayList >; kui kuskil väga lähedal on imeline Haskell // “about yourself” by one habrauser

Lubage mul tuua teile mõned numbrid. Keelekirjeldus Roman Duškini Haskelli keeleteates on vaid 100 lehekülge pikk. Ülejäänud 430 lehekülge hõivavad moodulite kirjeldused, mida mul pole veel kunagi vaja läinud. Raamatu köide Learn You a Haskell for Great Good! (muide, varsti ilmub ka venekeelne tõlge) on 400 lehekülge. Mulle isiklikult tundub, et soovi korral saab selle pooleks kahandada ilma midagi kaotamata. Võrdluseks, Kernighani ja Ritchie programmeerimiskeel C on 300 lehekülge pikk ja Stroustrupi C++ programmeerimiskeel peaaegu 1100 lehekülge pikk. Kas sa tõesti arvad raske keel, mille kirjeldus on ka kõige konservatiivsemate hinnangute kohaselt ligikaudu sama mahuga kui C-keele kirjeldus, mis on omakorda 3-4 korda lühem kui C++ keele kirjeldus?

Nüüd on aeg anda kood, mis näitab, nagu mõnele programmeerijale meeldib öelda, keele "suurt deklaratiivset olemust". Näiteks defineeritakse nii funktsioon, mis tagastab teatud arvu numbri jagajate loendi:

getDivisors nr
| nr< 1 =
| muidu = [ x | x<- [ 1 .. num] , num `mod ` x == 0 ]
-- ^ ilmselge optimeerimine -

Täpselt nagu definitsiooni kirjutamine! Kui tähistada arvu n jagajate hulk kui D(n) ja tõlkida ülal kirjutatu matemaatika keelde, saame D(n) = (x | x ∈ ℤ +, x ≤ n, mod(n, x) = 0), kus n ∈ ℤ + . Nüüd kirjutame funktsiooni numbri primaalsuse kontrollimiseks:

isAluarv
| nr<= 1 = False
| muidu = getDivisors num == [ 1 , arv]

Ja jälle on see, nagu kirjutaksite matemaatilist valemit. Nüüd saame nimekirja kõik algarvud:

allPrimeNumbers = [ 2 ] ++ filter isPrime [ 3 , 5 .. ]

Jah, Haskell saab koostööd teha lõputu nimekirjad. See on võimalik tänu laisale hindamismehhanismile. See tähendab, et ühtegi algarvude loendi elementi ei hinnata enne, kui neid kuskil tegelikult kasutatakse. Lisaks mugavusele võimaldab laisk hindamine kirjutada kiiremaid programme, millest tuleb juttu vastavas lõigus.

Kui arvate, et Haskelli "kõrge deklaratiivne olemus" kehtib ainult matemaatikaülesannete kohta, siis eksite. Vaadake näiteks, kuidas näeb välja GUI rakenduse kood Haskellis. Või kuidas pääseda juurde andmebaasile HaskellDB teegi abil. Tegelikult kasutab päring tavalist relatsioonialgebrat. Siin on eelis SQL-i ees see, et päringu õigsust kontrollitakse programmi koostamise etapis. Kui aga soovid kirjutada päringuid tavalises SQL-is, ei takista sind keegi.

Ja neil, kes võitlevad goto operaatori vastu, on hea meel teada saada, et Haskellil seda pole.

2. Automaatne mäluhaldus

Haskell võtab mäluhalduse üle täieliku kontrolli. Tsitaat WikiBooksist:

Funktsionaalsed keeled vabastavad programmeerija sellest raskest vastutusest. Mälu eraldatakse kaudselt, automaatselt ja spetsiaalne prügikoguja (prügikoguja) tagastab kasutamata mälutükid süsteemi. Optimaalsed mäluhaldusalgoritmid on keerulised, kuid tänaseks on need juba üsna hästi välja töötatud. Nende algoritmide kasutamine ei pikenda oluliselt programmi kui terviku tööaega (võrreldes sellega, kui programmeerija ise targalt mälu eraldab ja vabastab).

See tagab, et Haskelli programmid on vabad raskesti tuvastatavatest tõrgetest, nagu puhvri ületäitumine, nullviisorid, initsialiseerimata andmed ja mälulekked. Arvatakse, et paljudel inimestel on osutitega töötamisega raskusi, seega tundub nendest täielikult vabanemine olevat hea mõte.

Muide, mitte kõiki loetletud probleeme pole sellistes kõrgetasemelistes keeltes nagu Java, Perl ja Python päriselt lahendatud (ma üldiselt vaikin C++ probleemidest). Näiteks David Beasley raamatus on toodud näide programmist (minu koopia on lk 174), mis kasutab vaatleja mustrit, mille puhul prügikorjaja ei suuda mälu vabastada, kui just nõrkptr-i ei kasutata.

Inimesed, 21. sajand on käes! Kas me haldame mälu veel 90 aastat, kasutades selleks karkusid, nagu võrdlusloendurid ja nõrgad lülitid, või isegi käsitsi? Või unustame lõpuks, nagu halb unenägu, ja läheme edasi?

3. Puhtad funktsioonid

Funktsiooni kutsutakse deterministlik, kui selle tagastatav väärtus sõltub ainult argumentidest. Nad ütlevad funktsiooni ei oma kõrvalmõjusid, kui selle kutsumine ei kirjuta faili, loe pesast, muuda globaalseid muutujaid jne. Funktsiooni kutsutakse puhas, kui see on deterministlik Ja ei oma kõrvalmõjusid.

Haskell soovitab tungivalt kirjutada puhtaid funktsioone. Ükskõik kui mitu korda me funktsioonile samu argumente edastame, tagastab see alati sama tulemuse. Lisaks võime olla kindlad, et puhta funktsiooni kutsumisel ei kirjutata andmeid faili ega edastata üle võrgu. See lihtsustab oluliselt programmi arendamist.

Robert Martini raamatus "Puhas kood — kõrvalmõjude loomine, analüüsimine ja ümberkujundamine" on kirjas järgmine:

Kõrvalmõjud on valed. Teie funktsioon lubab teha üht, kuid teeb midagi muud, kasutaja eest peidetud. Mõnikord muudab see oma klassi muutujaid ootamatult - näiteks määrab neile funktsioonile edastatud parameetrite väärtused või globaalsed süsteemimuutujad. Igal juhul on selline funktsioon salakaval ja kahjulik vale, mis sageli viib ebaloomuliku ajastuse ja muude sõltuvuste tekkeni.

Kuid kas Haskell saab tõesti hakkama ilma failidest lugemise, võrguga töötamise ja kasutaja sisendita? Lõppude lõpuks on need kõik kõrvalmõjud. Järelikult ei saa vastavaid toiminguid teha puhastes funktsioonides.

Fakt on see, et Haskellis on funktsioonid jagatud puhtaks ja "määrdunud", st mittedeterministlikuks ja kõrvalmõjudega. Määrdunud funktsioone kasutatakse andmete sisestamiseks, puhastusfunktsioonidele edastamiseks ja tulemuse väljastamiseks. Teisisõnu, puhta funktsionaalse keele sees on mingi väike protseduuriline maailm. Või vastupidi, olenevalt sellest, kuidas te seda vaatate. Kõik geniaalne on lihtne!

4. Esitus

Ma kordan laiskade arvutuste kohta. Haskellis hakatakse midagi arvutama alles siis, kui seda tegelikult vaja on. Saate deklareerida 9000 elemendist koosneva loendi, kuid kui vajate neist ainult ühte (ja see ei sõltu loendi teistest elementidest), siis arvutatakse ainult selle ühe elemendi väärtus. Ja see põhimõte töötab kõikjal ja mitte ainult loendites. Mõnes C++ saab seda ka teha, aga vastava koodi pead ise kirjutama või mõne raamatukogu ühendama, aga Haskellis on kõik juba “kastist väljas”.

Kuna enamik Haskelli funktsioone on puhtad, saame antud argumentide jaoks funktsiooni tagastusväärtuse lihtsalt vahemällu salvestada. Kui funktsiooni kutsutakse mitu korda samade argumentidega, tehakse tegelik töö ainult üks kord. Teisel või kolmandal kõnel võetakse vahemälust juba arvutatud väärtus.

Vaatame teist näidet. Oletame, et meil on teatud funktsioon f(a, b) . Olgu argumendid a ja b mõnede muude funktsioonide arvutuste tulemused, mis õnneliku juhuse läbi on puhtad. Sel juhul saab a ja b arvutada paralleelselt! Nõus, mitmetuumaliste protsessorite ajastul on see oluline. OOP keeltes on sellist optimeerimist palju keerulisem teostada.

Lisaks jõudluse kasvule muudame programmi automaatselt paralleelseks ja sünkroniseerime täitmislõime! Kuid mitmelõimeline programmeerimine oma ummikutega on tänapäevase programmeerimise tähtsuselt peaaegu teine ​​probleem pärast käsitsi mäluhaldust. Kui arvate, et ülaltoodud on vaid teooria, vaadake GHC kompilaatorilaiendite loendit ja projekti Data Parallel Haskell. Kõik on juba ellu viidud!

Täiendus: Vaata ka Haskell STM projekti. Tehingumälu on huvitav, kuna see võimaldab vabaneda mitme keermega rakenduste lukkudest.

Kahjuks ei leidnud ma Haskelli programmide jaoks ühtegi võrdlusalust peale shootout.alioth.debian.org. "Kahjuks" - kuna mul on selle võrdlusaluse kohta palju küsimusi. Ma keeldun uskumast, et Pascali programmid töötavad 2 korda aeglasemalt kui C-programmid. Küsitavad on ka vead ±100% stiilis skriptikeelte puhul. Selle võrdlusaluse järgi on Haskell aga ülekaalukalt kiireim funktsionaalne programmeerimiskeel. Kiiruse poolest on see võrreldav Java ja C# keeltega.

5. Vähem refaktoreerimist

Avame raamatu Refactoring – Improving Fowler's Existing Code sisukorra ja vaatame, mis tüüpi ümberkujundamine on olemas. Meetodi ekstraheerimine, meetodi lisamine, meetodi liigutamine, ajutise muutuja sisestamine, ajutise muutuja asendamine meetodikutsega, selgitava muutuja sisestamine, ajutise muutuja poolitamine, viite asendamine väärtusega, väärtuse asendamine viitega. .. kas arvate, et suur osa ülaltoodust on Haskellis rakendatav, eeldusel, et Kas sellel keelel pole viiteid ega muutujaid (las ja kus ei loeta)?

Viisin hiljuti läbi kaks küsitlust Habré kohta, ühe Java ajaveebis ja teise Haskelli ajaveebis. Küsimus oli sama – "Kui sageli peate koodi ümber muutma keeleks %PROGRAMMING_LANGUAGE%?" Ma kaotasin nende küsitlustega peaaegu kogu oma karma (kas see tõesti kellegi silmale haiget teeb?), kuid see oli seda väärt:

Olen valmis tunnistama, et osa vastanutest võiks vastata, et nad Haskellis ei refaktoreeri, kuna nad ei kirjuta sinna, kuid tõenäoliselt see pilti oluliselt ei moonuta. Esiteks on küsitlustes “erapooletu” nupp ja teiseks ilmselt Java ajaveebi lugejate seas ei kirjuta kõik Java keeles.

Huvitaval kombel soodustab Haskelli süntaks ise suurest hulgast lihtsatest funktsioonidest koosneva koodi kirjutamist. Selle põhjuseks on asjaolu, et Haskelli koodile meeldib väga kasvada ekraani laiuse, mitte kõrguse järgi, nagu OOP-keeltes. Samuti, kui if-then-else konstruktsiooni sees on vaja midagi enamat kui lihtsalt väärtuse tagastamine, siis if-then-else on palju enamat mugav defineerida uus funktsioon. Tegelikult saab tänu mustrite sobitamisele ja valvamisele Haskelli kirjutada ilma kui-siis-muidu kasutamata.

Neil juhtudel, kui refaktoreerimine on siiski vajalik, toimub see tänu keele rangele staatilisele tippimisele lihtsalt ja lihtsalt.

6. Kas TDD ja UML on vajalikud?

Pidagem meeles, mida mis tahes Pythoni või Java õpik meile õpetab. Kõik on objekt. Kasutage objekte ja teie koodi on palju lihtsam hooldada, laiendada ja taaskasutada. Uskuge ja parandage meelt! Koodiga kaasas palume siiski teha klassiskeemid, muidu on sellest võimatu aru saada. Ja kindlasti kirjutage testid, mis hõlmavad nii palju O suurem osa koodist, vastasel juhul muudavad selle muudatused teie programmi töövõimetuks. Küsimused palun. Jah palun. Mida, mis, vabandust? Mis mõttes on see "lihtsalt protseduurilise programmeerimise lisand"?! Kas sa ei kuulanud? Kapseldamine, pärandumine, polümorfism!

Ei, tõesti, kas olete kunagi mõelnud, mitut karku peate OOP-i toimimiseks kasutama? Laadige alla vähemalt juba mainitud Radio-T 253. number ja kuulake, mida nad TDD kohta räägivad. Tegelikult palutakse meil teha kaks või isegi kolm korda (arvestades UML-i) rohkem tööd!

Inimesed küsivad mõnikord: "Mis on Haskelli UML-i vaste?" Kui minult 10 aastat tagasi selle kohta esimest korda küsiti, mõtlesin: „Mul pole õrna aimugi. Võib-olla peaksime oma UML-i välja mõtlema." Nüüd mõtlen: "Nad on lihtsalt tüübid!" // Simon Peyton Jones

Samas enamasti vaikitakse, et iga ülesanne lihtsalt OOP raamidesse ei mahu. Tegelikult on OOP end hästi tõestanud ainult mängude arendamisel, GUI-de ja graafiliste redaktorite loomisel. Minge näiteks CPAN-i. Võtke 10 juhuslikku moodulit ja vaadake, kui palju neist tõesti kasutage OOP-i (jah, Perlil on OOP), selle asemel, et pakkida moodul lihtsalt klassina. Või vaadake Hackage'i ja loendage, mitu moodulit lahendab üsna edukalt praktilisi probleeme ilma OOP-i vihjeta.

Märge: Tegelikult ei ole funktsionaalsed keeled kapseldamise, pärimise ja polümorfismi suhtes sugugi võõrad. Näiteks Haskellis toimub kapseldamine mooduli tasemel, polümorfism on tagatud tüübiklasside kaudu ja pärand on olemas puhtal kujul. OOP-idioomide kasutamise kohta Haskellis saate lugeda raamatu 14 meelelahutuslikku esseed Haskellist ja funktsionaalsest programmeerimisest neljandast peatükist.

Miks mulle tundub, et Haskelli kasutamisel peaks “karkusid” vähem olema? Noh, esiteks, kuna klasse pole (mitte segi ajada tüüpi klassid) — nende skeeme pole vaja joonistada :) Teoreetiliselt ei takista miski tüüpklasside skeeme joonistamast, aga isiklikult pole ma sellist kohanud. Teiseks, nagu oleme juba teada saanud, on Haskell “väga deklaratiivne” keel. See tähendab, et tavaliselt kirjutame sellele, Mida me tahame saada, mitte Kuidas. Sel viisil dokumenteerib programm ennast. Ja kolmandaks, keele range staatiline tippimine võimaldab teil programmi koostamise etapis kõrvaldada terved veaklassid. See ei välista vajadust aeg-ajalt teste kirjutada, kuid vähendab oluliselt nende arvu.

Muide, Haskelli olulisteks omadusteks on kõrvalmõjude puudumine ja keele platvormideülesus, pealegi päris platvormideülene, mitte nagu C++. Üks kord kirjutatud programm töötab võrdselt hästi mis tahes OS-i ja protsessori arhitektuuriga, ilma et oleks vaja kirjutada makrosid vms. See viib selleni, et Haskelli programmeerijad ei koosta sageli (!) oma programme üldse.

Ma tean, et see kõlab naeruväärselt. Ma ise ei uskunud seda enne, kui proovisin. See näeb välja umbes selline. Loome uue mooduli. Tutvustame paari uut tüüpi ja deklareerime esimese funktsiooni. Käivitame ghci tõlgi, testime funktsiooni tüüpiliste andmete ja paari servajuhtumiga. Kui see töötab, unustame äsja kirjutatud funktsiooni ja kirjutame järgmise. Kui kõik vajalikud funktsioonid on juurutatud, vaatame, millised olemid tuleks moodulist eksportida ja teeme vastavad muudatused. See on kõik, töö tehtud! Ilma ühegi kompilatsioonita. Kui programm töötab ghci-s, kompileerib iga Haskelli kompilaator selle mis tahes platvormi jaoks õigesti. See on tõeline platvormideülene vorm.

7. Lai kasutusala

Levinud väärarusaam Haskelli kohta on see, et keel sobib vaid kitsa hulga keeruliste matemaatiliste ülesannete lahendamiseks. Ja tõesti, kuidas peaksite lahendama "tavalisi" probleeme, kui meil pole isegi tsükleid? Tegelikult pole rekursioon halvem kui silmused. Iteratsioonides või rekursioonides mõtlemine on lihtsalt harjumuse küsimus. Ja ei, rekursioon ei pea tingimata kasutama mälu. Tegelikult viib OOP kuritarvitamine lõpuks malli järgi mõtlemiseni ja järeldusteni stiilis "ilmselt on see probleem pärit spordiprogrammeerimise olümpiaadist, kuna ma ei tea, kuidas seda lahendada".

Tuleme siiski tagasi ulatuse juurde. Nagu ma juba märkisin, sobib Haskell suurepäraselt GUI-rakenduste kirjutamiseks. Ma pole veel proovinud sellele veebi jaoks kirjutada, kuid arvustuste põhjal ei lähe Haskell siin kehvemini kui PHP:

Lõpetasin just Haskellis lihtsa FastCGI programmi kirjutamise. Tahtsin aru saada, kuidas Haskelli veebirakendused töötavad ja kas see keel sobib keeleõppele pühendatud veebilehe loomiseks. Haskell ei saanud mitte ainult tööd tehtud. Selgus, et selles kirjutamine on palju lõbusam kui PHP-s // jekor.com. Võite isegi Haskellis kirjutada.

Lõpuks, tahaksin tänada Roman Duškinit abi eest selle märkme kirjutamisel. Tänu temale on teksti oluliselt täiustatud ja ka rikastatud huvitavate faktidega funktsionaalse programmeerimise maailmast.

Loodan, et postitus oli teile huvitav. Nagu alati, ootan lugejate kommentaare või küsimusi.

Täiendus: Raamat "Õppige teile Haskell suureks hüvanguks!" venekeelset tõlget saab juba tellida aadressilt veebipoed. Internetist leiti ka Anton Kholomievi kasulik raamat “Haskelli õpik”. Kui teil "ei ole aega" Haskelli uurimiseks, proovige lugeda Grigori Makejevi õpikut, sellel on ainult 114 lehekülge.

Arvan, et kõik mu blogi püsilugejad teavad, et olen mõõdukalt tulihingeline jälgija. Asi pole selles, et ma ei tahaks kõigest muust midagi kuulda – ei, ma lihtsalt propageerin alati valikuvõimalust, arutelu ja pidevat parima otsimist ning seega lõppkokkuvõttes arendusmetoodikate teatud evolutsiooni. Isikukultus on minu jaoks sama ebameeldiv kui eraldi programmeerimismetoodika kultus, sest pikemas perspektiivis on see selge tee stagnatsiooni ja hullumeelsusse.

Seda õppetööd jätkates tahaksin täna peatuda ühel imelisel funktsionaalsel programmeerimiskeelel. Mulle on juba kolm korda saadetud sama küsimus: kust alustada (jätkada) Haskelli õppimist?

Ilmselt on aeg anda lühike vastus ja nõuanne. Siin on minu poolt selle teema sisestamise üldine algoritm.

0. etapp – sissejuhatus. Haskell? Mida kuradit?

Programmeerijate värbajate seas tuntud paradoks, mida sageli nimetatakse "", ja see on sõnastatud umbes järgmiselt:

Kui ettevõte valib peamiseks programmeerimiskeeleks vähekasutatud esoteerilise programmeerimiskeele, siis on sellel ettevõttel suurim võimalus saada endale turu parimad programmeerijad. Miks? Tõsiasi on see, et need programmeerijad, kelle jaoks uute asjade õppimine pole probleem, tahavad ennekõike sellise “veidra ettevõtte” palgata; need, kellele vähetuntud ja raskesti ligipääsetav ei ole takistuseks; ja lõpuks need, kellel on piisavalt kõrge enesehinnang, et end sellistes ilmselgelt karmides tingimustes pakkuda.

Ja mis kõige tähtsam: programmeerijaid on kahte tüüpi: need, kes õpivad selleks, et saada head tööd, ja nad valivad alati mainstream, sest see suurendab oluliselt nende võimalusi tööd leida; ja need, kellele lihtsalt meeldib midagi uut õppida, areneda ja nad valivad alati parim, mis on sageli kaugel kõige kasumlikum, nagu teevad nende kaaskarjeristid. Niisiis, Pythoni paradoks väidab, et alustades arendust tipptasemel eksootikaga, meelitate nagu tolmuimeja teise kategooria programmeerijaid (vastupidine kehtib ka tööd pakkuvate ettevõtete kohta).

Abstraktse näitena võin tuua täiesti sarnase etteantud omadustega fookusgruppide salajase sihtimise, loo oma hiljutisest noorusest. Kui ma veel õppisin, oli meil “veider” podpod, mis oli esitlemisel demonstratiivne matemaatiline analüüs ei pööranud kunagi tähelepanu publiku paremale poolele. See tähendab, et auditooriumis oli kaks rida - vasak ja parem - ja siin ta peab loengut, seletab midagi, aga samal ajal ei vaata KUNAGI õiget rida - kogu tema tähelepanu on ainult vasakust reast pärit õpilastel. . Samuti koos vastustega küsimustele – õiget sõidurada tema jaoks ei eksisteerinud. Ta ei kuule sealt midagi.

Selle tulemusena märkasin üle pika aja huvitavat asja: meie rühmas toimus automaatne enesetuvastus ja killustatus nendeks, kes matemaatiline analüüs vajalik ja huvitav (ja nad võtsid kohe kohad vasakpoolses reas, olles leppinud selle kummalise mängu reeglitega) ja need, kes seda ei vajanud, ja nad kiirustasid paremale istmetele, sest siia nad jäid. oma seadmetele. See sorteerimine toimus iseenesest, ilma täiendavate erakordsete pingutusteta väljastpoolt. Õpetaja jaoks, nagu ma tema motivatsioonist aru sain, oli see väga mugav - ta ei raisanud oma energiat sisuliselt võõraste peale, vaid lõi kohe tingimused oma energia ja tähelepanu koondamiseks neile, kes tema teadmisi vajasid. C õpilaste jaoks oli see samuti võidukas olukord.

3. Lava - uue keele sügavuse ja tunnetuse otsimine

Kolmas etapp kaevab juba sügavamale. Siinkohal teen ettepaneku murda veidi traditsioonilisi mustreid (ja ma oi, kuidas mulle seda teha meeldib) ja minna üle põhimõtteliselt teistsugusele teabe esitamise vormingule: see mitte ainult ei mitmekesista keeleõppe ülesannet, vaid hõlmab ka uusi, teie aju seni aktiveerimata piirkonnad (eriti kurname teie programmeerija kurnatud vasakut aju). Pean silmas suurepärast videoloengut Haskellist väga targalt inglise juurtega mehelt.

Siin on selle väljund:

Kursus Funktsionaalne programmeerimine Haskelli abil
(Keel: inglise)
35 tundi | 1280x720 | XviD – 1326Kbps
25,00 kaadrit sekundis | Mp3 – 96Kbps | 20,06 GB

Selle suure videoloengu saate alla laadida. Soovitan teil kiirustada, enne kui nördinud autoriõiguste valdajad jooksma hakkavad ja selle haruldase arhiivi asjata hävitama ( värskendada: jah, nad tabasid teda: aamen. Kutsun kõiki huvilisi seda mega-linki kasutama).

4. Viimane etapp on harjutamine

Sobivaima ja inspireerivama praktilise ülesande leidmine on eraldi postituse teema, kus tooksin mõned markantsed näited kuulsate inimeste elust läinud programmeerijad, nii et jätame minu selgitused selle viimase etapi kohta praegu kõrvale.

Midagi sellist, paljudele osaliselt ilmselget algoritmi, tahtsin anda kõigile, kes tahavad Haskelli õppida, ja mitte ainult. Tee seda!

Ja lõpuks, teiste programmeerimiskeelte austajatele:

Haskell on šamaanidele antud püha programmeerimiskeel Teemandimaa nende kõrgeim jumalus Komonada universaalse suhtlus- ja vaimse puhastusvahendina, mis sobib nii jumalikele olenditele kui ka (mõnedele) lihtsurelikele, kes on kannatanud raskete intellekti staadiumide all. Oma päritolu tõttu on keel alati olnud funktsionaalselt puhas. Haskelli õppimine algab keskmiselt 10-12-aastaselt. Treeningu varane alustamine tagab, et jõuate 75. eluaastaks jõu kolmandale tasemele. Te ei tohiks oma järgmisesse ellu edasi lükata seda, mida saate selles elus vähemalt alustada.