Funkcionális programozás 2. előadás Sapientia Egyetem, Műszaki és Humántudományok Tanszék Marosvásárhely, Románia mgyongyi@ms.sapientia.ro 2016, tavaszi félév
Miről volt szó? Programozási paradigmák: imperatív, funkcionális, logikai programozási technikák. Alaptípusok, megjegyzések használata. A funkcionális programozás, a Haskell programozási nyelv főbb jellemzői: feltételek megadása, rekurzió, mintaillesztés, szigorú, statikus típusosság, margó szabály, halmazkifejezések, magasabb rendű függvények.
Miről lesz szó? lambda kifejezések függvénykompozícíó magasabb rendű függvények részleges paraméterezése feltételes kifejezések a Haskell kiértékelési stratégiája GHC parancsok könyvtármodul importálása alaposztályok a tuple típus, könyvtárfüggvények
Lambda kifejezések A függvények egy alternatív definiálási módja. 1. feladat: a megadott paraméter értékét 1-el növeli: my_inc :: (Num a) => a -> a my_inc = \x -> x + 1 Prelude> my_inc 23.5 24.5 2. feladat: a lista minden egyes elemének értékét 1-el növeli: l_inc :: (Num a) => [a] -> [a] l_inc ls = map (\x -> x + 1) ls Prelude> l_inc [1..10] [2,3,4,5,6,7,8,9,10,11]
Függvénykompozíció A matematikából ismert művelet megfelelője. 3. feladat: a lista páratlan elemeinek meghatározása (kiszűrése) paratlanl :: (Integral a) => [a] -> [a] paratlanl ls = filter (not. even) ls Prelude> paratlanl [1..20] [1,3,5,7,9,11,13,15,17,19] A függvényparaméter elhagyható: paratlanl_1 :: (Integral a) => [a] -> [a] paratlanl_1 = filter (not. even) Prelude> paratlanl_1 [1..20] [1,3,5,7,9,11,13,15,17,19]
Függvénykompozíció 4. feladat: a korábbi my_inc meghívása my_twice2 :: (Num a) => a -> a my_twice2 = my_inc. my_inc Prelude> my_twice2 12 14 5. feladat: init, tail könyvtárfüggvények Prelude> init [1..10] [1,2,3,4,5,6,7,8,9] Prelude> tail [1..10] [2,3,4,5,6,7,8,9,10] suffl :: [a] -> [a] suffl = (init.tail) Prelude> suffl "gezakekazeg" "ezakekaze"
Magasabb rendű függvények részleges paraméterezése angol terminológia: partial parameterization, curry-zés, Haskell Curry angol matematikus neve után, a függvényhívás megengedett kevesebb paraméterrel is, mint ahogy az a függvény szignaturájában meg van határozva 6. feladat: adott szám hatványértékei 0-tól 10-ig (a kitevő változik): hatv1 :: (Integral a) => a -> a -> a hatv1 x n n < 0 = error "Negativ kitevo" n == 0 = 1 mod n 2 == 0 = hatv1 (x*x) (div n 2) otherwise = x * hatv1 (x*x) (div n 2) fugv1 :: (Integral a) => a -> [a] fugv1 x = map (hatv1 x) [0..10] > fugv1 3 [1,3,9,27,81,243,729,2187,6561,19683,59049]
Magasabb rendű függvények részleges paraméterezése 7. feladat: a számok hatványai 0-tól 10-ig (az alap változik): hatv2 :: (Integral a) => a -> a -> a hatv2 n x n < 0 = error "Negativ kitevo" n == 0 = 1 mod n 2 == 0 = hatv2 (div n 2) (x*x) otherwise = x * hatv2 (div n 2) (x*x) fugv2 :: (Integral a) => a -> [a] fugv2 n = map (hatv2 n) [0..10] > fugv2 3 [0,1,8,27,64,125,216,343,512,729,1000]
Magasabb rendű függvények részleges paraméterezése 8. feladat: adott szám hatványértékei 0-tól 10-ig (a kitevő változik): fugv3 :: (Integral a) => a -> [a] -> [a] fugv3 kit ls = map ((\x n -> x ^ n) kit) ls > fugv3 3 [0..10] [1,3,9,27,81,243,729,2187,6561,19683,59049] 9. feladat: a számok hatványai 0-tól 10-ig (az alap változik): fugv4 :: (Integral a) => a -> [a] -> [a] fugv4 alap ls = map ((\n x -> x ^ n) alap) ls > fugv4 3 [0..10] [0,1,8,27,64,125,216,343,512,729,1000]
Magasabb rendű függvények részleges paraméterezése 10. feladat: válasszuk ki egy adott listából az x-el osztható számokat: oszthato :: (Integral a) => a -> a -> Bool oszthato x y mod y x == 0 = True otherwise = False fugv :: (Integral a) => a -> [a] -> [a] fugv x ls = filter (oszthato x) ls > fugv 7 [1..100] [7,14,21,28,35,42,49,56,63,70,77,84,91,98]
Feltételes kifejezések Másik függvény az oszthatóság vizsgálatára: oszthato1 :: (Integral a) => a -> a -> Bool oszthato1 x y = if mod y x == 0 then True else False Másik függvény a hatványozásra: hatv3 :: (Integral a) => a -> a -> a hatv3 x n = if n < 0 then error "negativ kitevo" else if n == 0 then 1 else if mod n 2 == 0 then hatv3 (x*x) (div n 2) else x * hatv3 (x*x) (div n 2) > hatv3 2 100 1267650600228229401496703205376 Az if a Haskell-ben nem utasítás (vagy álĺıtás), hanem egy feltételes kifejezés, ezért az else ág kötelező.
A Haskell kiértékelési stratégiája funkcionális programozási nyelvek esetén kétféle kiértékelési stratégiát ismerünk: lusta (lazy), mohó (eager) a Haskell kiértékelési stratégiája lusta, Lusta kiértékelési stratégia: a legbaloldalibb, legkülső redex (redukálható kifejezés) helyettesítése történik először, pontosabban egy alkifejezés csak akkor értékelődik ki, ha szükség van az értékére (ha a kifejezés függvénymegadással kezdődik előbb a függvénydefiníció lesz alkalmazva) mindig megtalálja a normál formát, ha az létezik Pl. Clean, Haskell, Miranda lehetségessé válik a függvények kötetlen definiálása: egy függvény akkor is képes értéket visszaadni, ha egyik argumentuma nem definiált, lehetségessé válik a végtelen adatszerkezetek létrehozása, a mohó kiértékelési stratégiához képest kevésbé hatékony.
A mohó (eager) kiértékelési stratégia a legbaloldalibb, legbelső redex, az argumentumok helyettesítése történik meg először, nem mindig ér véget a kiértékelési folyamat, hatékonyabb mint a lusta rendszer, Pl. Lisp, SML, Hope, a lusta kiértékelési stratégia hatékonyságát oly módon lehet javítani, hogy az azonos részkifejezéseket megjelöljük, az eredményt megjegyezzük és ahányszor szükség van rá mindig a megjegyzett eredményt használjuk.
Példa, kiértékelési stratégiákra my_inc :: Num a => a -> a my_inc x = x+1 negyzet :: Num a => a -> a negyzet x = x*x negyzet_inc :: Num a => a -> a negyzet_inc x = negyzet (my_inc x) > negyzet_inc 6 A lusta kiértékelési stratégia: negyzet_inc 6 -> negyzet(my_inc 6) -> (my_inc 6)*(my_inc 6) -> (6+1) * (6+1) -> 7 * 7 -> 49 A mohó kiértékelési stratégia: negyzet_inc 6 -> negyzet( my_inc 6 ) -> negyzet( 6 + 1 ) -> negyzet( 7 ) -> 7 * 7 -> 49
GHC parancsok mindegyiket lehet rövidített formában is használni, csak a parancs kezdőbetűjével: :reload fnev az fnev.hs nevű állomány újrabetöltése, :type kif a kif kifejezése típusának a lekérdezése, :? az összes GHC parancs lekérdezése, :quit kilépés a GHC-ből, :set +t a kiértékelés után a kifejezés típusa is megjelenik :unset +t az előző beálĺıtás visszavonása :set +s a kiértékelés után megjelenik az eltelt idő és a lefoglalt bájtok száma stb. Fenntartott szavak: case class data default deriving do else if import in infix infixl infixr instance let module newtype of then type where
Könyvtármodul importálása import Data.Char my_isdigit :: Char -> Bool my_isdigit x = x >= 0 && x <= 9 > my_isdigit 3 True > isdigit 3 True > isdigit w False > import Data.List > tails "hello" ["hello","ello","llo","lo","o",""]
Alaposztályok az Eq azokat a típusokat tartalmazza, amelyek az egyenlőség és nem egyenlőség operátorokkal összehasonĺıthatóak: (==) :: a -> a -> Bool (/=) :: a -> a -> Bool az Ord magába foglalja az Eq osztályba tartozó típusokat és a sorba rendezhető típusokat (Bool, Char, String, Int, Integer, Float, Double, Lista, Tuple): (<) :: a -> a -> Bool (<=) :: a -> a -> Bool (>) :: a -> a -> Bool (>=) :: a -> a -> Bool min :: a -> a -> Bool max :: a -> a -> Bool
Alaposztályok a Show azokat a típusokat tartalmazza, amelyek értékei átalakíthatóak karakterlánccá (String-é). magába foglalja az alaptípusokat (Bool, Char, String, Int, Integer, Float, Double, Lista, Tuple): show :: a -> String > show 579 "579" > show ( b, True) "( b, True)"
Alaposztályok a Read azokat a típusokat tartalmazza, amelyek értékét meg lehet határozni karakterláncból, magába foglalja az alaptípusokat (Bool, Char, String, Int, Integer, Float, Double, Lista, Tuple): read :: String -> a > read "False" :: Bool False > read "[10, 11, 12, 13]" :: [Int] [10, 11, 12, 13]
Alaposztályok a Num azokat a típusokat tartalmazza, amelyeknek numerikus értékük van magába foglalja az Eq és Show osztályokba tartozó típusokat és a következő alaptípusokat (Int, Integer, Float, Double) (+) :: a -> a -> a (-) :: a -> a -> a (*) :: a -> a -> a negate :: a -> a abs :: a -> a signum :: a -> a nem rendelkezik osztási operátorral
Alaposztályok az Integral magába foglalja a Num osztályokba tartozó típusokat és azokat a típusokat is amelyeken egész osztás és maradékos osztás végezhető (div) :: a -> a -> a (mod) :: a -> a -> a
Alaposztályok a Fractional magába foglalja a Num osztályokba tartozó típusokat és azokat a típusokat is amelyeken valós osztás és reciprok érték határozható meg (/) :: a -> a -> a (recip) :: a -> a > recip 2.0 0.5
A standard Haskell osztálydiagramm
A rendezett n-es (tuple) típus különböző típusú elemek (értékek) halmaza. Jelölésére a kerek zárójelt használjuk: (), az elemek száma rögzített. > let my_tuple = ("Marika", 3, 8.75) > let (nev, evf, jegy) = my_tuple > print nev > "Marika" > print evf > 3 > print jegy > 8.75 egy 2 elemű tuple típuson alkalmazhatóak az fst és snd könyvtárfüggvények: > let my_tuple1 = ("Marika", 8.75) > fst my_tuple1 > "Marika" > snd my_tuple1 > 8.75
A tuple típus 12. feladat: másodfokú egyenlet gyökeinek a meghatározása, az eredmény egy tuple típus gyok :: Double -> Double -> Double -> (String, Double, Double) gyok a b c = if delta < 0 then error "Komplex gyokok" else if delta == 0 then ("egyforma gyokok, ", x1, x1) else ("ket gyok", x1, x2) where x1 = (-b + sqrt delta) / nev x2 = (-b - sqrt delta) / nev delta = b * b - 4 * a * c nev = 2 * a > gyok 1 3 2 ("ket gyok",-1.0,-2.0) > gyok 1 4 4 ("egyforma gyokok, ",-2.0,-2.0)