Kezdőlap
Ez a weboldal azért készült, mert nem találtam magyarul,számomra tetsző tananyagot a Haskell
nyelvhez. A haskell-el az ELTE funkcionális programozás nevű tárgyán találkoztam és a weboldal megírásához az ott tanult információkat használtam fel.
A weboldalt markdownbook (mdBook) segítségével írtam.
Tananyag
A Haskellt az egyetemen is két tárgy keretében tanítják és én is ezt a szerkezetet követem.
A Kezdő
részben találhatóak az alapvető információk és a haskell nyelvi eszközei.
A Haladó
részben a nehezebb típusosztályokkal(Functor
, Applicative
, Monad
, ...), és az IO
(ki- és bemenet)-hoz szükséges tananyag található.
Hasznos linkek
Credits due where credits due
Elte Funkcionális programozás weboldala: lambda
markdownBook: mdBook
A tananyagot lektorálta:
A weboldalt készítette: GeRRy aka "Java"
Haladó tananyag
A haladó tananyaghoz erősen ajánlott a Kezdő
menűpont alatt található leckék elolvasása, mert olyan fogalmakat használok, amik ott vannak elmagyarázva.
Kezdő tananyag
Telepítés
A telepítéshez ajánlott ghcvup használata, amiben automatikusan lehet kezelni a verziókat.
A telepítési útmutató megtalálható az alábbi linken: ghcup
Felépítés
-
- lecke: Funkcionális programozás, típusok
-
- lecke: Függvények
Első lecke
Fogalmak: Tiszta nyelv, statikusan típusos, deklaratív, funkcionális
A Haskell funkcionális nyelv
Tiszta nyelv
Haskell tiszta nyelv, ez annyit jelent hogy nincsenek benne mellékhatások.
(Haladó anyagban pontosítva lesz a fogalom: ha van mellékhatás akkor az a típusból kiderűl.)
Statikusan típusos
Mindennek van típusa. Ha nem írtuk ki akkor is van, csak a fordító találja ki egyéb adatok alapján.
Deklaratív nyelv
Ahelyett hogy vezérlési folyamatokat írnánk le, parancsokat/lépéseket sorolnánk fel, azt írjuk le hogy mi a kívánt eredmény. A fordító eldönti hogy mi a legoptimálísabb módja ennek.
Funkcionális nyelv
A fő struktúrális elem a függvény, szokás mondani hogy "Haskellben minden függvény" (vagy kulcsszó). Megpróbálnak programozásban matematiaki függvényeket írni. A nyelv szintaxisa is hasonló a matematikához.
GHC - GHCI
A GHC a haskell compilerének neve, Glasgow Haskell Compiler. A GHC fordítja a haskell kódot gépi kódra.
A GHCI egy interaktív Haskell környezet. A GHCI-ben ki tudjuk próbálni a különböző függvényeket, kifejezéseket. Terminálból futtatható.
GHCI parancsok
- :l :load <Elérési út> - Betölti a fájlt
- :r :reload - Újratölti a legutolsó betöltött fájlt
- :t <függvény> - megmutatja a függvény típusát
- :i <függvény/típus/típusosztály> információt ad a paraméterként megadott dologról
Típusok, konstansok
A lecke elkezdése elött érdemes elolvasni a fogalmak című részt is, de legalább a ghci parancsokat ismerni (benne van a foglamak között).
Típusok
Haskellban a típusokat mindig nagybetűvel kell kezdeni.
A legtöbbet használt típusok:
Int -- egész szám (32/64 bit architektúrától függően)
Integer -- egész szám, limitje a memória mérete
Float -- lebegőpontos szám
Double -- dupla pontosságú lebegőpontos szám
Bool -- logikai érték, True vagy False
Char -- karakter, 'a', 'b', '1', '!', '?', ... apostrofok között
String -- karakterlánc, "Hello World", "Haskell", "1", "2", ... idézőjelek között
Konstansok
A konstansok olyan függvények amiknek nincs paramétere, így az értéke mindig ugyanaz.
Típusszignatúra
A típusszignatúra a függvény típusát írja le. A függvény neve után kettősponttal elválasztva kell megadni. A típusokat nyilak választják el egymástól. A legutolsó típus a visszatérési érték típusa.
pl:
egy :: Int
inc :: Int -> Int
Első lecke
A függvények nevét általában kisbetűvel kezdjük, de lehet használni speciális karaktereket is, ezeket a típusszignatúrában zárojelbe kell tenni. A függvény paramétereit szóközzel elválasztva adjuk meg.
Paraméterek
Haskellban olyan értelemben nem léteznek változók mint más nyelvekben (hiszen az egy mellékhatás hogy megváltozik az értéke), a függvény paramétereire szoktak változóként hivatkozni.
A paraméterek nevét kissbetüvel szokás kezdeni.
Számok
egy :: Int
egy = 1
-- ez egy konstans függvény, mindig 1-et ad vissza
inc :: Int -> Int
inc a = a + 1
-- megnöveljük eggyel a kapott számot
-- inc 5 = 6
mult :: Int -> Int -> Int
mult a b = a * b
-- mult 5 6 = 30
kettőharmad :: Double
kettőharmad = 2 / 3
nagyonNagySzám :: Integer
nagyonNagySzám = 99 ^ 1000
- Integer végtelen addig amíg van memória, nem okoz gondot kiírni és kiszámolni ezt a számot
- Ha Int lenne a típusa akkor többször is Int overflow lenne, azaz amint eléri a legnagyobb Int elemet (32 biten 2^31-1, 64 biten 2^63-1) akkor negatív számokat kezdene kiírni.
- A legnagyobb és legkisebb Int számokat a
minBound
ésmaxBound
függvényekkel lehet lekérdezni.- minBound :: Int, maxBound :: Int
Létező függvények számokra:
(+) :: Int -> Int -> Int
, összeadás(-) :: Int -> Int -> Int
, kivonás(*) :: Int -> Int -> Int
, szorzásdiv :: Int -> Int -> Int
, egész osztás (csak egész számokon működik)(/) :: Double -> Double -> Double
, tört osztásmod :: Int -> Int -> Int
, maradékos osztás
Logikai értékek
true :: Bool
true = True
-- konstans true függvény, mindig True-t ad vissza
false :: Bool
false = False
-- konstans false függvény, mindig False-t ad vissza
and :: Bool -> Bool -> Bool
and a b = a && b
Létező függvények Bool típusra:
(&&) :: Bool -> Bool -> Bool
, és függvény(||) :: Bool -> Bool -> Bool
, vagy függvénynot :: Bool -> Bool
, negálja a paramétert (True -> False, False -> True), más nyelvekben!
,~
jelölik(==) :: Bool -> Bool -> Bool
egyenlőség vizsgálat(/=) :: Bool -> Bool -> Bool
nem egyenlő
Szöveg
A String
típus egy lista Char
típusú elemekből. A listákat késöbb, a harmadik leckében fogjuk részletesebben megismerni.
char :: Char
char = 'a'
ello :: String
ello = "ello"
hello :: String
hello = 'H' : "ello"
world :: String
world = "World"
helloWorld :: String
helloWorld = hello ++ " " ++ world
-- helloWorld = "Hello World"
Létező függvények String típusra:
(:) :: Char -> String -> String
, hozzáfűz egy karaktert a string elejéhez (legelső elemnek)(++) :: String -> String -> String
, két stringet fűz összewords :: String -> [String]
, a stringet szavakra bontjaunwords :: [String] -> String
, a szavakat összefűzi egy stringgélines :: String -> [String]
, a stringet sorokra bontjaunlines :: [String] -> String
, a sorokat összefűzi egy stringgé
Második lecke
Fogalmak: Mintaillesztés, kiértékelés, hibák
Mintaillesztés típusai
A mintaillesztést a gyakorlati részben részletezem.
Mintaillesztés szempontjából két típusu függvényt különböztetünk meg:
- totális függvény: minden lehetséges bemenetre(paraméterre) definiált
- parciális függvény: nem minden lehetséges bemenetre(paraméterre) definiált
Kiértékelés típusai
A kiértékelést olyan szempontból vizsgálhatjuk hogy mikor hajtodik végre:
- lusta (lazy) kiértékelés: egészen addig nem foglalkozik az értékkel amíg az nem szükséges
- mohó (strict) kiértékelés: az értéket azonnal kiértékeli
Esetleges hibák
Hibák szempontjából is kétfélét különböztetünk meg:
- fordítási hiba: olyan hiba amit a fordító talál meg, például ha nem megfelelően írunk egy függvényt
- ghci-ben: error, általában piros színnel, fájl betöltéskor fordul elő, hosszú üzenet a hiba leírásáról
- futási hiba: olyan hiba ami a program futásakor keletkezik
- ghci-ben:
*** Exception
-nel kezdődik
- ghci-ben:
Függvényhasználat, mintaillesztés, kiértékelés
Függvényhasználat, kötési erősség
A függvényeket lehet prefix
és infix
módon használni.
Infix módon a két paraméteres függvényeket lehet használi.
A kötési erősség határozza meg hogy melyik függvény fog először végrehajtódni pl.: +
vagy *
.
Prefix
A függvény neve elöl áll pl.: f 2 3
Prefix módon használt függvények kötési erőssége 10
tehát 1 + f 2 3 esetben nem kell zárójelezni mert az f erősebb mint a +.
f :: Int -> Int -> Int
f a b = a + b
-- f 2 3 -- 5
Infix
A függvény neve középen áll pl.: 2 `f` 3 és a fügvényt `` karakterek közé kell tenni.
Ilyenkor kétféle kötést különböztetünk meg infixr
és infixl
.
Ha a függvényünk infixr
akkor jobbra zárójelez, ha infixl
akkor balra.
f :: Int -> Int -> Int
f a b = a + b
infixr 5 `f` -- így adható meg az általunk definiált függvény infix kötése
Infix függvények használata perfixként?
Ha a függvény nevét zárójelek közé tesszük akkor perfixként is használhatjuk.
(+) 5 6
(*) 2 3
Mintaillesztés
A mintaillesztés segítségével tudjuk a függvényeket több esetre bontani, olyan mintákat irhatunk le amire az adott típus illeszkedni tud:
numbers :: Int -> Int
numbers 1 = 2
numbers 2 = 4
isTrue :: Bool -> Bool
isTrue True = True
isTrue False = False
isBchar :: Char -> Bool
isBchar 'b' = True
isBchar 'B' = True
A mintákon lineárisan, egymás után halad végig és figyeli hogy illeszkedik-e rá a paraméter.
A fenti példában az isTrue
függvény totális, a numbers
és isBChar
is parciális függvények.
Ha a numbers
függvénynek paraméterül adunk egy 3
-at akkor a futási hibát kapunk, ugyanis nem deklaráltuk hogy a 3
-mal mit csináljon a numbers
függvény.
Az isTrue
függvénynek bármilyen Bool
típusú paramétert adhatunk, mivel minden Bool
típusú paraméterre definiált.
"Joker" minta, elnevezett minta
Ha _ teszünk a minta helyére akkor az azt jelenti, hogy az értékét nem használjuk fel, ezért ez bármilyen típusra illeszkedni fog.
Egyéb esetben akármilyen névvel elnevezhetjük a mintát, amennyiben az nem létező függvény, kulcsszó vagy korábban használt elnevezés.
numbers :: Int -> Int
numbers 1 = 2
numbers 2 = 4
numbers valamilyenszam = valamilyenszam
-- így a függvény már totális
isTrue :: Bool -> Bool
isTrue ertek = ertek
-- fölösleges mintát illeszteni, ha ugyan azt az értéket adjuk vissza
isBchar :: Char -> Bool
isBchar 'b' = True
isBchar 'B' = True
isBchar _ = False
-- így a függvény már totális
addNumbers :: Int -> Int -> Int
addNumbers a a = a + a --hibás definició
Infix definícióban
Függvény definiálásakor is lehet használni az infix formáját.
f :: Integer -> Integer -> Integer
a `f` b = a + b
infixr 5 `f`
Kiértékelés
A Haskell lusta nyelv, ha nem muszáj nem számol.
threeBool :: Bool -> Bool -> Bool -> Bool
threeBool a b c = a && b && c
ha a függvénynek adunk egy False értéket akkor a függvény nem fogja kiértékelni a többi paramétert, mivel a && operátor bal oldali paramétere False, így a függvény visszatérési értéke is False lesz
Ez az && fügvény lustán történő definiálása miatt van:
(&&) :: Bool -> Bool -> Bool
(&&) True b = b
(&&) False _ = False
ez azt is fogja jelenteni, ha a fügvénynek valamilyen futási hibát okozó értéket adnánk át akkor az se fog kiértékelődni
- pl.: threeBool False True (2 `div` 0) -> False
constTrue :: Bool -> Bool
constTrue _ = True
constFalse :: Bool -> Bool
constFalse érték = False
a lustaság ugyanúgy vonatkozik a mintaillesztésre is, ha nincs felhasználva az = jobb oldalán akkor nem fogja kiértékelni
- pl.: constTrue (4 `div` 0) -> True
- pl.: constFalse (10 `div` 0) -> False
Harmadik lecke
Adatszerkezetek, Rendezett n-es, Listák
Adatszerkezetek
Homogén adatszerkezet
Csak azonos típusú elemeket tárol
Heterogén adatszerkezet
Kűlönböző típusú elemeket is tárolhat.
Rendezett n-es
A rendezett n-es egy olyan adatszerkezet, amelyben az elemek sorrendje számít. A rendezett n-eseket a következőképpen definiáljuk:
(<elem1>, <elem2>, <elem3>, ..., <elemN>)
Eredetileg maximum 62 elemet tartalmazhat (újabb verziókban 64), de csak a 15.-ik elemig vannak támogatva. Ez azt jelenti, hogy ha egy 15-nél nagyobb rendezett n-esünk van akkor azon nem fogunk tudni egyenlőséget vizsgálni, ghci konzolban nem tud megjelenni.
Lista
Haskellben a listát láncolt listaként valósították meg. A lista két részből áll: fejelem és a maradék lista. A maradék lista is lista, így a lista rekurzívan (Lásd:Negyedik lecke) definiálható.
A listában csak egyféle típusu elemek lehetnek, így homogén adatszerkezet.
A lista felépítése:
1 : 2 : 3 : 4 : []
Ahol [] az üres listát jelöli.
Ami "szintaktikus cukorka" segítségével egyszerűen leírható:
[1,2,3,4]
A láncolt lista jellegéből következik, hogy az elemeket csak a lista elejére tudjuk beszúrni, a végére nem.
Tuple, Lista, Listagenerátor
Tuple
A rendezett n-esek közül a leggyakrabban használt a rendezett kettes, amelyet tuple-nak is neveznek. A tuple-t a következőképpen definiáljuk:
(<elem1>, <elem2>)
A tuple-re vannak előre definiált függvények, amelyek segítségével könnyen hozzáférhetünk az elemekhez.
fst :: (a,b) -> a
fst (x,_) = x
snd :: (a,b) -> b
snd (_,y) = y
Lista
A String is egy karakter lista (String = [Char]).
"Hello" == ['H','e','l','l','o']
Listagenerátor
A lista generátor egy kifejezés, amely egy listát ad eredményül.
Egyszerű listagenerátor
Négy függvényt lehet használni listagenerálásra, mindegyiknek létezik "szintaktikus cukorka" alakja:
- enumFromTo 1 10 == [1..10] -> [1,2,3,4,5,6,7,8,9,10]
- enumFromThenTo 1 3 10 == [1,3..10] -> [1,3,5,7,9]
- enumFrom 1 == [1..] -> [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15... Végtelen lista]
- enumFromThen 1 3 == [1,3..] -> [1,3,5,7,9,11,13,15... Végtelen lista]
Amennyiben nem adjuk meg az egész szám típusát a fordító automatikusan Integert választja, így lehetséges végtelen listát létrehozni. Ha a számnak Int típusa van akkor a lista nem végtelen.
[(1::Int), ((maxBound::Int)-1)..] == [1,9223372036854775806]
Ez leegyszerűsíthető a következő alakra:
[<kezdőérték> (, <lépésköz>) .. (<végérték>)]
Halmazszerű listagenerátor
Létezik egy a matematiaki halmazokat utánzó leírása is:
[<kifejezés> | <változó> <- <lista>]
Ezzel kifejezhető például a következő halmaz: {x^2 | x ∈ {1..5}}
[x^2 | x <- [1..5]]
A <változó> <-
Több generátort is lehet használni egy listagenerátorban, ezeket vesszővel választjuk el egymástól.
[(x,y) | x <- [1..3], y <- [1..2]]
Ez a kifejezés a generátorok Descartes szorzatát adja eredményül.
[(1,1),(1,2),(2,1),(2,2),(3,1),(3,2)]
Szűrőfeltétel
A listagenerátorokban lehetőség van szűrőfeltétel megadására is. A szűrőfeltételt a generátorok után vesszővel elválasztva kell megadni.
[x | x <- [1..], x `mod` 2 == 0]
Ez a kifejezés a páros számok listáját adja eredményül.
evenList :: [Integer]
evenList = [x | x <- [1..], x `mod` 2 == 0]
evenList = [2,4,6,8,10,12,14,16,18,20,22,24,26,28,30...] Végtelen lista
Mintaillesztés listára
A listák mintaillesztésére is van lehetőség. Mintát lehet illeszteni pontos elemszámra, illetve a lista első elemére és a maradékára.
null' :: [a] -> Bool
null' [] = True -- üres lista
null' _ = False
singleton :: [a] -> Bool
singleton [_] = True -- egy elem van a listában
singleton _ = False
twoOrMore :: [a] -> Bool
twoOrMore (_:_:_) = True -- legalább két elem van a listában
twoOrMore _ = False
firstTwo :: [a] -> (a,a)
firstTwo (x:y:_) = (x,y)
-- parciális függvény, ha a lista kevesebb mint két elemű akkor execption-t dob
keepFirstThree :: [a] -> [a]
keepFirstThree (x:y:z:_) = [x,y,z]
keepFirstThree (x:y:_) = [x,y]
keepFirstThree [x] = [x]
keepFirstThree _ = []
Létezik a head
és tail
függvény, amelyek segítségével a lista első eleméhez és a maradékához lehet hozzáférni, valamint a last
és init
függvények, amelyek segítségével a lista utolsó eleméhez és a lista első elem nélküli részéhez lehet hozzáférni.
```haskell
head' :: [a] -> a
head' (x:_) = x
tail' :: [a] -> [a]
tail' (_:xs) = xs
last' :: [a] -> a
last' [x] = x -- egy elem van a listában
last' (_:xs) = last' xs -- rekurzió: Negyedik lecke
init' :: [a] -> [a]
init' [x] = [] -- egy elem van a listában
init' (x:xs) = x : init' xs -- rekurzió: Negyedik lecke
Mint látható mindegyik függvény parciális így nem tudnak kezelni üres listát. A head
, tail
, last
és init
függvényeket csak akkor használjuk, ha tudjuk hogy a lista nem üres.