710 likes | 824 Views
Funkcionální programování Haskell. Jan Hric, KTI ML MFF UK,1997- 2005 http://kti ml .ms.mff.cuni.cz/~hric/ Jan.H ric@mff.cuni.cz. Funkcionální programování. Historicky: lambda-kalkul, cca. 1940 LISP, 1959-1960 ...Scheme, ML, Haskell Literatura:
E N D
Funkcionální programováníHaskell Jan Hric, KTIML MFF UK,1997-2005 http://ktiml.ms.mff.cuni.cz/~hric/ Jan.Hric@mff.cuni.cz
Funkcionální programování • Historicky: • lambda-kalkul, cca. 1940 • LISP, 1959-1960 • ...Scheme, ML, Haskell • Literatura: • - Richard Bird, Philip Wadler, Introduction to Functional Programming • Prentice Hall, New York, 1988 • jazyk Miranda • Simon Thompson: Haskell: The Craft of Functional Programming, Addison-Wesley, 1997 • http://www.haskell.org -> impl. Hugs (,GHC) • - elektronicky: tutorial.ps,.dvi
Jazyk Haskell • funkcionální jazyk • silně typovaný • polymorfizmus, parametrický (vs. podtypový v C++) • implicitní typování (systém si typy odvodí) • líné vyhodnocování • nekonečné d.s. • referenční transparentnost • ... další rysy • porovnávání se vzorem - pattern matching (místo selektorů) • funkce vyšších řádů • lexikální rozsahy platnosti, „dvourozměrná“ syntax (indentace) • typové třídy (uživatelsky definované), přetížené funkce • čistý jazyk (bez sideefektů), spec. podpora V/V • moduly (N/I)
Základy • interpretační cyklus: • čti - vyhodnoť - vytlač • (read - eval -print) • vyhodnocení výrazu • každý výraz má hodnotu (a typ) • zdrojový text v souboru • -- komentáře • {- vnořené komentáře -} • prelude.hs (standard.pre) -- předdefinované funkce ... • prompt ? (dříve, nyní > ) • ? :quit • ? :? help, seznam příkazů • ? :load “myfile.hs”
Skripty a interakce • ? 3*4 • 12 • delka = 12 • kvadrat x = x*x • plocha = kvadrat delka • ? kvadrat (kvadrat 3) • 81 • Kontext, prostředí - obsahuje dvojice: proměnná a hodnota
Výrazy a hodnoty • redukce • výrazu podle pravidla-definice fce • kvadrat(3+4) =>(3+4)*(3+4) -- kvadrat • => 7*(3+4) -- + • => 7*7 -- + • => 49 -- * • líné vyhodnocování: volání funkce se nahrazuje tělem, za formální argumenty se dosadí (nevyhodnocené) aktuální arg., arg. se vyhodnocuje pouze pokud je potřeba • při uvažování o programech spec. hodnota: nedefinováno – chyba, nekonečný výpočet┴
Hodnoty a typy • 5 :: Int – příklady zápisu hodnot a jejich typů • 10000000000 :: Integer – dlouhá čísla • 3.0 :: Float • ’a’ :: Char ’\t’,’\n’,’\\’,’\’’,’\”’ • True :: Bool ; False :: Bool – v prelude • [1,2,3] :: [Int], 1:2:3:[]::[Int] • ”abc” :: [Char] – retězce, String • (2, ’b’) :: (Int, Char) – dvojice, n-tice • succ :: Int -> Int – typ funkce • succ n = n+1 – definice funkce • *špatně [2,’b’] – hodnoty musí mít stejný typ
Typy • základní: Int, Integer (nekonečná přesnost), Bool, Char, Float • složené • n-tice (1,’a’):: (Int,Char), (a,b,c) ... speciálně ( ) :: ( ) • seznamy: [a] = [] | a : [a] • typy funkcí: Int -> Int • (uživatelsky definované) • (typové) proměnné: a, b, ... v popisu typu • ! uvádět typ není nutné, ale odvozuje se (a kontroluje se -> typová chyba) • suma::[Int]->Int, length::[a]->Int • length [] = 0 • length (a:xs) = 1 + length xs • (typové třídy – def. přetížených fcí, stejné jméno pro různé typy)
Převody typů • explicitní volání převodních funkcí • toEnum :: Int -> Char-- předdefinováno, zjednoduš. (přetížení) • fromEnum :: Char -> Int --lze def. pro výčt. typy • ord = fromEnum -- • chr = toEnum • offset = ord ’A’ – ord ’a’ • capitalize :: Char -> Char • capitalize ch = chr (ord ch + offset) • isDigit :: Char -> Bool • isDigit ch = (’0’ <= ch) && (ch <= ’9’)
Funkce • Definice v souboru - skriptu • funkce se aplikuje na argumenty a vrací výsledek • curried forma (podle Haskell B. Curry) • “argumenty chodí po jednom” • f :: Int -> (Int -> Int) • místo: f :: (Int, Int) -> Int • f (3,4) = ... • f 3 4, (f 3) 4 => ...
Int, Bool a vest. fce • :: Int -- typ celých čísel • běžné aritmetické operátory • +,*,^,-,div,mod :: Int -> Int -> Int –nepřesné, je přetížené • abs, negate:: Int -> Int • :: Bool , výsledky podmínek, stráží • > >= == /= <= < :: a->a->Bool –nepřesné (1) • && || :: Bool -> Bool -> Bool • not :: Bool->Bool • – (1) některé typy nelze porovnávat, např. funkce a typy funkce obsahující
Příklady • maxi :: Int -> Int -> Int • maxi n m • | n>=m = n -- stráže/guards • | otherwise = m • allEq :: Int->Int->Int->Bool • allEq n m p = (n==m)&&(m==p) • const :: a-> b->a • const c x = c • Volání const c :: b->a je pro c::a konstantní funkce • ? const 7 (1.0/0.0) -- bez chyby kvůli línému vyhodnocení • 7
Příklad • fact3 :: Int -> Int • fact3 n = if n==0 then 1 • else n*fact3 (n-1) • ? fact3 4 • 24 • iSort :: [Int] -> [Int] • iSort [] = [] • iSort (a:x) = ins a (iSort x) • ins :: Int -> [Int] -> [Int] • ins a [] = [a] -- ins cmp a [] = [a] • ins a (b:x) -- ins cmp a (b:x) • | a<=b = a:b:x -- zobecnění | cmp a b = … • | otherwise = b : ins a x • s porovnávací fcí cmp: (i obecnější typ) - parametrizace kódem • ins :: (a->a->Bool)->a->[a]->[a] , iSort :: :: (a->a->Bool)->[a]->[a]
Lexikální konvence • identifikátory • post. písmen, číslice, ‘(apostrof), _(podtržítko) • funkce a proměnné - zač. malým písm. • velké zač. písm - konstruktory • vyhrazeno: • case of where let in if then else data type infix infixl infixr primitive class instance module default ... • operátory • jeden nebo víc znaků • : ! # $ * + - . / \ < = > ? @ ^ | • spec. : ~ • symbolové konstruktory začínají znakem : • identifikátor v `(zpětný apostrof): 5 `mod` 2 • závorky pro neoperátorové užití operátorů: (+), (+) 3 4
Syntax - layout • 2D layout, offside pravidlo: definice, stráže (|), case, let, where ... • - umožňuje vynechání separátorů • - zapamatuje se sloupec zač. definice fce, cokoli je napravo, patří do definice • nsd n m • | n==m = • n • | n>=m = nsd m (n `mod` m) • | otherwise = nsd m n • explicitní skupina v {}, jako separátor znak ; • -více definic na jednom řádku
Podmínky • fact3 :: Int -> Int • -- neošetřuje záporné vstupy • fact3 n = if n==0 then 1 • else n*fact3 (n-1) • fact2 n | n==0 = 1 • | n>0 = n*fact2 (n-1) • | otherwise = error “in: fact2”
Porovnávání se vzorem • pattern matching (není to unifikace, pouze “jednostranná”) • implicitní podmínka na místě formálního parametru • i pro uživatelsky def. typy • length1 :: [a] -> Int • length1 [] = 0 • length1 (x:xs) = 1 + length1 xs --záv. nutné • -- vzorn+k • fact1 0 = 1 • fact1 (n+1) = (n+1)*fact1 n • -- vzor _ • and :: (Bool,Bool) -> Bool -- necurryované • and (True,True) = True -- and není líné • and (_ ,_) = False • and2 (False, _) = False -- líná verze • and2 (_ , x) = x
Lokální definice: where, let • lokální def. k fci uvádí klíč. slovo where (na nejv. úrovni) • norma :: Complex->Float • norma cnum = sqrt(re^2+im^2) • where re = rpart cnum -- def. vzáj. rekurzívní • im = ipart cnum • c = (3.0,4.0) -- testovací data • ? let re=rpart c; im=ipart c in sqrt(re^2+im^2) • 5.0 -- c = (3.0,4.0) • rpart :: Complex -> Float • rpart (re,_) = re -- definice selektorů • ipart :: Complex -> Float • ipart (_,im) = im
Operátory • závorky pro deklaraci operátoru • (&&&)::Int->Int->Int -- maximum • a&&&b = maxi a b • převod : op na fci, bin. fce na op. • ekvivalentní: (+) 3 4 , 3+4 • 5 `div` 2 , div 5 2 • (deklarace operátorů) • aplikace fce váže nejvíc: • fact 3 + 4 vs. fact (3+4) • ** abs -12 ; abs (-12) • Sekce: (+), (1+) (+1)::Int->Int -- pro všechny operátory
Operátory • 9-0 ; 9 nejvyšší priorita , 0 nejnižší • funkční volání .. váže těsněji než 9 • 9,doleva asoc. !! -- n-tý prvek • 9,doprava . -- skládání fcí • 8,doprava ^ ^^ ** • 7,doleva * / `div` `mod` `rem` `quot` • 6,doleva + - • i unární - • 5,doprava : ++ neasoc \\ • 4,neasoc == /= < <= > >= `elem` `notElem` • 3,doprava && -- and • 2,doprava || -- or
Definice operátorů • vyšší priorita jako v matematice • pouze binární operátory • infixl 6 + -- sčítání, doleva asoc. • infixr 5 ++ -- append, doprava asoc. • infix 4 == -- porovnávání, neasoc. • infix 4 `elem` -- prvek seznamu, neasoc.
Odlišnosti aplikačního programování • bezstavové, nemá (klasické) proměnné • neobsahuje příkazy, např. přiřazení • neobsahuje sekvenci příkazů • výraz je “definovaný” svým výskytem (jako aktuální parametr) • použije se jen 1x • Program pracuje s celými d.s. (seznamy , stromy …) • Program vzniká (obvykle) skládáním z jednodušších funkcí (ne jako posloupnost změn položek d.s.)
Syntax • case, if - porovnávání se vzorem • stráže • let, where - vložené definice • \ x -> f(x) - definice funkcí (lambda) • dvourozměrná syntax (layout) • stručné seznamy • data - definice nových uživatelských datových typů • type - typové synonyma • type Complex = (Float,Float) • (rozšiřující část)
Seznamy • data List a = [] • | a : (List a) • - ve skutečnosti vestavěný typ [a] • length [] = 0 • length (x:xs) = 1+length xs • map :: (a->b)->[a]->[b] • map f [] = [] • map f (x:xs) = f x : map f xs • ? map (+1) [2,3,4] -- [3,4,5] :: Int • ? map (+) [2,3,4]-- [(2+),(3+),(4+)] :: [Int -> Int] • ? map (const 1) “abc” -- [1,1,1] :: Int • DC: zip :: [a]->[b]->[(a,b)]
Konstruktory seznamu • - konstruktory jsou automaticky vytvořeny z definice typu • [] :: [a] -- polymorfní konstanta • (:) :: a -> [a] -> [a] • -- data [a] = [] | a : [a] • pseudokod, lexikálně nesprávný • symbolové konstruktory musí začínat ‘:’, jinak to jsou funkce • konvence: [1,2,3] je výraz 1:2:3:[]
Dělenířádku na slova • type Word = String • splitWords :: String -> [Word] • splitWords st = split (dropSpace st) • split :: String -> [Word] • split [] = [] • split st = (getWord st):split(dropSpace(dropWord st)) • dropSpace st = dropWhile (\x->elem x whitespace) st • dropWord st = dropWhile (\x->not(elem x whitespace)) st • dropWhile :: (t -> Bool) -> [t] -> [t] • dropWhile p [] = [] • dropWhile p (a:x) • | p a = dropWhile p x • | otherwise = (a:x) • getWord st = takeWhile (\x-> not(elem x whitespace)) st • DC: takeWhile p x = … • whitespace = [‘ ‘, ‘\t’, ‘\n’]
Uživatelské typy – nerekurzivní • data Bool = False | True -- předdef. • data Color = Red | Green | Blue • data Point a = Pt a a -- polymorfní • data Maybe a = Nothing | Just a • data Union a b = Left a | Right b • Bool, Color, Point,… jsou jména typů • False, Red, Pt ... jsou jména konstruktorů • Pt má dva argumenty, False, Red, Nothing ... jsou konstanty a mají nula arg., ... • porovnávat se vzorem lze i uživ. typy • Pt 1 2 :: Point Int -- hodnoty jsou konkrétního typu • Pt ’a’ ’b’ :: Point Char • vestavěné typy nejsou speciální; liší se lexikálně, ne sémanticky
Rekurzivní uživ. typy • data Tree a = Leaf a • | Branch (Tree a) (Tree a) • data Tree2 a = Void • | Node (Tree2 a) a (Tree2 a) • konstruktory jako funkce • přístup na složky: porovnávání se vzorem, case, sel. funkce leftSub,.. • Leaf :: a -> Tree a • Branch :: Tree a -> Tree a -> Tree a • leftSub :: Tree a -> Tree a • leftSub (Branch l _)= l • leftSub _ = error “leftSub: unexpected Leaf” • př.: seznam listů stromu, ++ je spojení seznamů • listy :: Tree a -> [a] • listy (Leaf a) = [a] • listy (Branch l r) = listy l ++ listy r • -- listy (Branch l r) = append (listy l) (listy r)
Regulární typy • - n-arní stromy • data NTree a = Tr a [NTree a] • regulární typy: typové konstruktory (NTree) použité na pravé straně definice mají stejné argumenty (typové proměnné) jako na levé straně definice • nejde: • data Twist a b = T a (Twist b a) | Notwist • data Vtree a = In ( Vtree (a,a)) | … -- vyvážené stromy
Typové synonyma • Klíčové slovo type • na pravé straně od = jsou jména jiných typů • data Barva = Trefy | Kara | Srdce | Piky • data Hodnota = ... • type Karta = (Barva,Hodnota) • type RGB = (Int, Int, Int)
Komplexní aritmetika • komplexní číslo jako dvojice realných č. • type Complex = (Float,Float) • cadd :: Complex -> Complex -> Complex • cadd (rx,ix) (ry,iy) = (rx+ry,ix+iy) • -- implicitní typ fce cadd je obecnejší • ... • ? cadd (1.0,0.0) (0.1,2.0) • (1.1,2.0)
Case • obecné porovnávání se vzorem • case (proměnná, ...)of • (vzor, ...) -> výraz • ... • take n xs vybere ze seznamu xs prvních n prvků, pokud existují • take2 :: Int -> [a] -> [a] • take2 n xs = case (n,xs) of • (0,_) -> [] • (_,[]) -> [] • (n+1,x:xs) -> x : take2 n xs
Př.:Take • take, ekvivalentní (a obvyklý) zápis • take n xs vybere ze seznamu xs n prvků, pokud existují • take1 0 _ = [] • take1 _ [] = [] • take1 (n+1) (x:xs) = x : take1 n xs
Podmíněný výraz • if výraz je “syntaktický cukr” • ife1thene2elsee3 • ekvivalentní • casee1of True -> e2 • False -> e3
Funkce • funkce se konstruují tzv. lambda-abstrakcí • succ x = x+1 -- obvyklý zápis • succ = \ x -> x+1 -- ekv. lambda výraz • add = \ x y -> x+y -- víc parametrů • formální parametry mají rozsah platnosti tělo definice • succ a add se vyhodnotí na (interní) repr. daných fcí • funkci lze aplikovat na argument (!mezerou) • typy: když x::a, e::b, pak \x->e :: a->b • anomymní funkce - jako parametry fcí vyšších řádů • referenční transparentnost: volání fce na stejných parametrech vrátí stejnou hodnotu • protože nemáme “globální” proměnné, přiřazení a sideefekty
Funkce 2 • na funkcích není definována rovnost • funkci můžu aplikovat na argumenty • “aplikace je (jediný) selektor” • => lze zjistit její hodnotu v jednotlivých “bodech” • skládání fcí • (.) :: (b->c) -> (a->b) -> (a->c) • f . g = \ x -> f (g x) • -- (f . g) x = f (g x) -- ekviv. • odd :: Int -> Bool • odd = not . even • -- odd x = (not.even) x -- ekv. zápis
Funkce 3 • currying / curryfikace: argumenty chodí po jednom • typ a->b->c místo (a,b)->c • výhoda: lze částečně aplikovat • sekce: př: (+1), (1+), (+) • (x op) ekv. \y ->x op y, (op y) ekv. \x ->x op y • (op) ekv. \x y-> x op y • harmonicky_prumer :: [Float] -> Float • harmonicky_prumer xs = • (fromInt(length xs)/) • (sum (map (1/) xs))
Aritmetika s testováním chyb • lift2M :: (a->b->c)->Maybe a -> Maybe b -> Maybe c • lift2M op Nothing _ = Nothing • lift2M op _ Nothing = Nothing • lift2M op (Just x) (Just y) = Just (x ‘op’ y) • minusM :: Maybe Float -> Maybe Float -> Maybe Float • minusM x y = lift2M (-) x y • delenoM x y=if y==Just 0 then Nothing --vyvolání chyby • else lift2M (/) x y • ? deleno(Just 3)(lift2M (-)(Just 2)(Just 2))-- 3/(2-2) • Nothing • data AV a = AV a :-: AV a | AV a :/: AV a | Con a | … • eval :: AV Integer -> Maybe Integer • eval (av1 :/: av2) = delenoM (eval av1) (eval av2) • eval (av1 :-: av2) = lift2M (-) (eval av1) (eval av2) • eval (Con x) = Just x • ? catch (eval (Con 3 :/: (Con 2 :-: Con 2)) ) • catch Nothing opr_data = opravna_fce opr_data • catch (Just x) _ = x
Stručné seznamy 1 • analogické množinám {x | x in Xs & p(x)} • př. použití: seznam řešení - simulace nedeterminizmu • př.: [ x | x <- xs, p x ] -- filter p xs • př.: [ f x | x <- xs] -- map f xs • vlevo od ‘|’: výraz – pro každou hodnotu generátorů se přidá v. • vpravo od ‘|’: generátor (x<-xs) nebo podm. (stráž) (nebo let) • pořadí generátorů: zleva doprava • ? [(x,y)| x<-[1,2], y<-[3,4]] • ~~> [(1,3),(1,4),(2,3),(2,4)] • delitele n = [d| d<-[1..n], n `mod` d ==0] • spaces n=[’ ’| i<-[1..n]] -- i se nepoužije(C) • (list comprehensions)
Stručné seznamy 2 • testy (stráže) jsou boolovské výrazy • quicksort [] = [] • quicksort (x:xs) = quicksort [y|y<-xs, y<x ] • ++ [x] • ++ quicksort [y|y<-xs, y>=x] • - mírně neefektivní, xs se prochází 2x • podobně: aritmetické posloupnosti • [1..5] ~~> [1,2,3,4,5] • [1,3..8] ~~> [1,3,5,7] --rozdíl určuje krok • [1,3..] ~~> [1,3,5,7,.. --nekonečný seznam
Strategie vyhodnocování • redukční krok: nahrazení některého volání fce její definicí, přičemž se formální parametry nahradí aktuálními (v dosud vyhodnoceném stavu) • redukční strategie: vybírá první redukční krok • strategie: eager (ML, Scheme); • normální (-kalkul); • líná (Haskell); • Věta: všechny redukční strategie, pokud skončí, vydají stejný výsledek • Věta: pokud nějaká strategie skončí, pak skončí i normální (a líná) • sq x = x*x • př: sq( sq 3)
Strategie vyhodnocování 2 • eager (dychtivá), striktní red. strategie, volání hodnotou (zleva zevnitř): • sq(sq 3) ~> sq (3*3) ~> sq 9 ~> 9*9 ~> 81 • - každý argument se vyhodnocuje právě jednou • normální red. strategie, volání jménem (zleva zvnějšku): • sq(sq 3)~> sq 3*sq 3 ~> (3*3)*sq 3~>9*sq 3 ~> 9*(3*3)~>9*9~>81 • - nepoužité argumenty se nevyhodnocují • - nevýhoda: opakované vyhodnocování spol. podvýrazů (1)
Líná redukční strategie • líná: jako normální, ale neopakuje vyhodnocování argumentů (spol. podvýrazů) • sq(sq 3) ~> (x:=)sq 3*x(=sq 3)~> (3*3)*x(=3*3)->9*x(=9)~>81 • vyhodnocuje potřebné výrazy (argumenty) právě jednou • ? my_if True (0/7)(7/0{-BUM-}) --líně: bezchybně • my_if p x y = if p then x else y -- dychtivě: • - grafová redukce (cca 1986) vs. termová redukce • - využívá referenční transparentnost– nezáleží, kdy se vyhodnocuje • - umožňuje nekonečné datové struktury • - vyhodnocování v case a pattern matching: vyhodnotí tolik z výsledku na konstruktory, aby se dala určit jednoznačně správná větev pro další výpočet • - (méně efektivní implementace než striktní strategie: nejprve zapisuje neredukovaný tvar, pak redukovaný; při přístupu nutno zjišťovat, zda je term už redukovaný)
Nekonečné d.s. • líné vyhodnocování umožňuje potenciálně nekonečné d.s. • použijeme jen konečnou část (anebo přeteče paměť) • numsFrom n = n : numsFrom (n+1) • fib = 1 : 1 : [a+b | (a,b) <- zip fib (tail fib)] • factFrom = map fact (numFrom 0) • Autotest: efektivnější faktoriál • idea: prvky seznamu budou dvojice (n,f(n))
Nekonečné dat. struktury • několik užitečných procedur • repeat x = x : repeat x • cycle xs = xs ++ cycle xs • iterate f x = x : iterate f (f x) • [ f 0 (x), f 1 (x), f 2 (x), f 3 (x), …] • př.: jednotková matice • jedn_mat = • iterate (0:) -- další řádky, 0: se přidá na začátek • (1:repeat 0) -- prvnířádek 1:0:0:0:… • jedn_mat = [ 1:0:0:0: … , • 0:1:0:0: … , • 0:0:1:0: … , • …
Nekonečné dat. struktury a fce • - funkce psát kompatibilně s líným vyhodnocováním: • and1 True True = True -- není líná ve 2. arg. • and1 _ _ = False • and2 True x = x -- je líná • and2 False _ = False • - zpracování nekonečných struktur: • mít fce, které odeberou pouze potřebnou část d.s. (take, takeWhile, ... getWord) • mít fce, které zpracují nekon. d.s. na nekon. d.s. • (map, zip, filter, drop, dropWhile ...) • past: [ x | x<-[0..], x<4 ] ~~> [0,1,2,3, • striktní funkce: vyhodnotí vždy svůj argument (úplně) • Pro striktní funkce platí : f ~>
Polymorfní typy • - odvozování typů unifikací • f :: (t,Char) -> (t,[Char]) • f (x,c) = (x,[‘a’..c]) • g:: (Int,[u])->Int • g (m,l) = m+length l • h = g.f -- (.):: (b->c)->(a->b)->(a->c) • -- (t,[Char]) unifikuj s (Int,[u]) • h::(Int,Char)->Int
Polymorfní typy • polymorfní konstanty, funkce: • každý výskyt se typuje zvlášť • length([]++[1])+length([]++[True]) – OK • :: Int -> Int :: Bool -> Int --obecně :: a -> Int • proměnná: má pouze jeden (polymorfní) typ • * length(x++[1])+length(x++[True]) -- NE x::?
Typové třídy - idey • ne všechny operace jsou definovány na všech typech • typová třída: abstrakce těch typů, které mají definovány dané operace • - tento mechanizmus odpovídá ad hoc polymorfizmu, tj. přetížení • klíčová slova: • class - zavedení typové třídy • instance - definování typu jako prvku typové třídy, spolu s definováním operací • typový kontext: podmínky na typy • eqpair::(Eq a),(Eq b) => (a,b)->(a,b)->Bool • eqpair (x1,x2) (y1,y2) = x1==y1 && x2 ==y2