22 - Funkcionális programozás
Lusta vs modó kiértékelés
Szükséges fogalmak:
strict semanticscall-by-name- A kiértékelés az argumentum hivatkozásakor történik
- Ha az argumentumok sehol nem olvassa ahívó fél, nem kerül kiértékelésre
- Ha az argomentum értékét több helyen is használja a hívó fél, minden alkalommal kiértékelődik
sharing- Írható/olvasható referencia átadása a hívó félnek
Mohó kiértékelés
Implementációja a szigorú szemantikának, imperatív programozási nyelveknél megszokott.
Minden hívási helyen az argumentum kiértékelésre kerül.
A viselkedés a call-by-value stratégiára hasonlít, mivel minden értéket először meg kell szerezni a függvéníy meghívása előtt.
A függvény értéke definíció szerint undefined, ha valamelyik paramétere undefined.
Szigorú szemantika: egy
undefinedparaméterundefinedfüggvény eredményt okoz még akkor is, ha a függvény nem használja fel a paramétert kiértékeléskor
Lusta kiértékelés
Másnéven call-by-need, ami gyakorlatilag call-by-name és sharing keveréke.
Az argumentum csak annak felhasználásakor kerül kiértékelésre, viszont egy tisztán funkcionális környezetben minden érték nem módosítható, így az egyszeri kiszámolás után a referencia megosztható a többi meghívási helyen is.
Ez továbbra is megengedi azt, hogy a nem használt argumentumokat sosem értékelünk ki, valamint azon nyelvek sebességét is elérjük, amik a call-by-reference alapján minden hivatkozást megosztanak egymással, megőrizve a konzisztenciát a tiszta funkcionalitás által.
Szigorú szemantikával ellentétben ha az
undefinedparamétereit a függvény nem használja fel kiértékeléskor, a függvény annak értékével helyesen visszatér.
Hivatkozási helyfüggetlenség
Az azonos kifejezések értéke mindig ugyanaz
Statikus típusozottság
A kifejezések típusa fordítási időben ismert
Curry-féle elv
Egy, több paramétert váró függvény átalakítása egy olyan függvénnyé, ami az első paraméter alapján egy másik függvényt ad vissza, aminek szintén csak egy függvényre van szüksége
f :: (a, b, c) -> d
-- Curry átalakítással
g :: a -> b -> c -> d
-- Zárójellel
g :: a -> (b -> (c -> d))
??? title="Prelude" A Haskell Prelude ajánl erre függvényt
Haskell-ben minden függvény curry-zettnek értelmezett, ezen gondolatmenet engedi meg a parciális függvényalkalmazást, ahol a függvény alkalmazásának 'részeredménye' tovább adható, mint egy már kevesebb paramétert váró függvény.
margó szabály
Indentáció-érzékeny nyelveknél (mint amilyen Haskell is) a kódblokkok egymástól megkülönböztetése a konzisztens beljebbhúzásokkal történik.
A margó szabály (off-side rule) másik megnevezése a szignifikáns behúzás (significant indentation)
alaptípusok
IntIntegerFloatDoubleBoolCharString[a]
konverziók
fromIntegral :: (Integral a, Num b) => a -> b- Átalakít bármilyen
Integral-ból (like Int, Integer) bármilyenNum-ra, amit ki tud inferálni
- Átalakít bármilyen
round,floor,ceiling,truncate- (
(RealFrac a, Integral b) => a -> b) - hasonlóan, mint a
fromIntegral
- (
toInteger :: Intergral a => a -> IntegertoRational :: Real a => a -> RationalrealToFrac :: (Real a, Fractional b) => a -> b- hasonlóan, mint a
fromIntegral
- hasonlóan, mint a
show :: Shkow a => a -> Stringwords :: String -> [String]/unwords :: [String] -> Stringlines/unlines- Ugyanaz mint a
wordscsak sortöréssel
- Ugyanaz mint a
ord :: Char -> Int- (Unicode code-point)
chr :: Int -> ChardigitToInt- számjegy karakteréből
Int-et csinál Data.Char
- számjegy karakteréből
függvények definiálása és típusozása
név :: (Megkötés a, Megkötés b ...) => Paraméter1 -> Paraméter2 ...
név a b ... = f a ...
Példa
Tekintve, hogy Haskell képes egy szintig a típust inferálni, így egyes esetekben a függvény típusának megadása kihagyható
Totális függvények
Azon függvények, amik minden lehetséges bemenetre megoldással szolgálnak
Parciális függvények
Azon függvények, amik nem minden esetben szolgálnak eredménnyel, potenciális hibalehetőséggel (a szándékosan hibát dobó függvények is parciálisnak minősülnek, hiszen a Haskell-nem nincs hibakezelés)
Mintaillesztés
A függvény paraméterei mintákra illeszthetőek, és ha a minta illeszkedik, akkor a paraméterek értéke a minta megfelelő helyére kerül.
A minták kiértékelése mindig felülről lefelé történik, tehát a legelső minta, ami illeszkedik a paraméterekre kerül kiértékelésre.
Ha egy minta sem illeszkedik, akkor a program futásidejű hibát dob. (totális vs. parciális függvény)
!!! title="EA"
Egy függvényt több alternatívával (vagy szabállyal) is definiálhatunk. A függvényalternatíváknak egymás mellett kell elhelyezkedni a modulban.
A függvényparaméterek helyén minták vannak. A minta fogalmával a következő diákon ismerkedünk meg.
A függvényalternatívákat a kiértékelés során fentről lefelé próbáljuk végig.
Minta lehet:
- Változó: `x, xs, a, ...`
- Joker: `_`
- Típus specifikus minták: `True, 0, (a, b), ...`
őrfeltételek, esetszétválasztás
A mintaillesztés mellett a függvények paraméterei őrfeltételekkel is kiegészíthetőek, amik a mintaillesztés után kerülnek kiértékelésre.
Minden őrfeltétel egy Bool értékre kell kiértékelődjön, amely ha True-t ad vissza, akkor a kód belép azon ágba.
Az otherwise kulcsszó a True értéket adja vissza, így a legutolsó őrfeltétel mindig igaz lesz, ha az előzőek nem teljesültek.
rekurzió
Egy rekurzív függvény egy olyan függvény, ami a saját magát hívja meg.
A rekurzív függvényeknek jobb esetben van egy bázis esete, ami megállítja a rekurziót, és egy rekurzív eset, ami a bázis esetet hívja meg.
Amennyiben a bázis eset nem teljesül, a rekurzív eset hívja meg önmagát, amíg el nem érjük a bázis esetet.
Ha a rekurzív függvény nem tartalmazn bázis esetet, akkor a program verem túlcsordulással álhat meg, mivel a rekurzió sosem áll meg.
lokális definíciók
where és let .. in lehetőséget ad csak egy függvény kontextusában egy névtelen függvényt létrehozni, ami nem hívható meg máshol.
Hasznos segédfüggvények és accumulator-ok definiálására.
reverse'' :: [a] -> [a]
reverse'' [] = []
reverse'' xs = reverseAcc xs []
where
reverseAcc [] acc = acc
reverseAcc (y:ys) acc = reverseAcc ys (y:acc)
reverse''' :: [a] -> [a]
reverse''' [] = []
reverse''' xs =
let
reverseAcc [] acc = acc
reverseAcc (y:ys) acc = reverseAcc ys (y:acc)
in
reverseAcc xs []
magasabbrendű függvények
Azt mondjuk, hogy egy függvény magasabbrendű, ha: - egy másik függvényt vár paraméterül - egy másik függvényt ad vissza
Itt a f függvény egy másik függvényt vár paraméterül, és egy listát ad vissza, aminek minden eleme a bemeneti lista megfelelő elemének a g függvénnyel való kiértékelése.
Példák magasabbrendű függvényekre a Haskell Prelude-ben:
- map
- map :: (a -> b) -> [a] -> [b]
- A bemeneti lista minden elemére alkalmazza a megadott függvényt
- filter
- filter :: (a -> Bool) -> [a] -> [a]
- A bemeneti lista minden elemére alkalmazza a megadott függvényt, és csak azokat adja vissza, amik True-t adnak vissza
névtelen függvények
A névtelen függvények (lambda függvények) olyan függvények, amiknek nincs neve, és csak egyszer használatosak.
A Haskell-ben a \ karakterrel kezdődnek, és a paraméterek után egy -> karakter következik, ami a függvény törzsét jelöli.
függvénykompozíció
(.) :: (b -> c) -> (a -> b) -> a -> c- Visszaad egy olyan függvényt, ami a bemeneti két függvényt egymás után fogja alkalmazni a paraméterre
- Magasabbrenddű fügvényeknél hasznos használni
halmazkifejezések (list comprehensions)
Matematikai alak: \(\left{n^2\,|\, n\in \mathbb{N}\,|\,\text{n páros}\right\}\)
A Haskell-ben a listák halmazkifejezésekkel is definiálhatóak, amik a matematikai halmazkifejezésekhez hasonlítanak.
A fenti példa egy listát ad vissza, aminek minden eleme a [1..10] lista páros elemei.
A | karakter után a lista elemeinek generálására szolgáló kifejezés található, ami a x <- [1..10] részben található.
A , karakter után található kifejezés a szűrő, ami a lista elemeit szűri.
A fenti példa a [2, 4, 6, 8, 10] listát adja vissza.
típusosztályok
A típusosztály lényegében metódusok halmaza, és minden típus, ami egy típusosztályból származik, garantálja, hogy a benne foglalt metódusokat definiálta.
Legegyszerűbb példa a Num típusosztály, ami felett (többek között) az összeadás (+) és kivonás (-) művelete értelmezett, így annak minden leszármazottjáról tudjuk, hogy annak elemei össze adhatóak (Int, Integer, ...)
??? title="Az Ord típusosztály definíciója"
class Eq a => Ord a where
(<), (<=), (>=), (>) :: a -> a -> Bool
x <= y = x < y || x == y
x < y = not (x >= y)
x >= y = x > y || x == y
x > y = not (x <= y)
Ord alosztálya az Eq osztálynak
Eq ősosztálya az Ord osztálynak
??? title="deriving" Az Eq, Ord, Enum, Show, Read példányosítása rábízható a fordítóra. Ezt az igényünket az adattípus definíciójánál a deriving kulcsszóval kell jeleznünk:
```hs
data Maybe a
= Nothing
| Just a
deriving (Eq, Ord, Show, Read)
```
parametrikus (paraméteres) és ad-hoc polimorfizmus
Parametrikus polimorfizmus
A függvény a megadott típus sajátosságait nem használja fel, általánosan működik a legtöbb típussal
Ad-hoc polimorfizmus
A függvényt a típusosztály várja el, viselkedése eltérhet típusok között
típusszinonimák
Típusszinonimák segítségével új típusokat hozhatunk létre, amik a már meglévő típusok aliasai.
Crazy shit.
algebrai adattípusok definiálása
sum type:
product type:
ADT: