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

    1. lecke: Funkcionális programozás, típusok
    1. 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 és maxBound 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ás
  • div :: Int -> Int -> Int, egész osztás (csak egész számokon működik)
  • (/) :: Double -> Double -> Double, tört osztás
  • mod :: 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ény
  • not :: 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 össze
  • words :: String -> [String], a stringet szavakra bontja
  • unwords :: [String] -> String, a szavakat összefűzi egy stringgé
  • lines :: String -> [String], a stringet sorokra bontja
  • unlines :: [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

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.

Láncolt lista ábrája

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.

Láncolt lista beszúrás

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ó> <- kifejezést generátornak nevezzük.

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.

Negyedik lecke

Modulok, Rekurzió

Ötödik lecke

Típusosztályok, Polimorfizmus

Hatodik lecke

Vég-rekurzió

Hetedik lecke

Magasabbrendű függvények

Nyolcadik lecke

Hajtogatás

Kilencedik lecke

Fogalmak: Type, Data, NewType

Saját típusok

Tizedik lecke

Instance

Tizenegyedik lecke

Rekurzív Data

Nyolc királynő probléma