(S)DT Haskell Functioneel programmeren
Bijkomend materiaal de Haskell website Op de "Learning Haskell" pagina vind je veel bijkomende informatie. Je vindt er online materiaal, bijvoorbeeld Learn You a Haskell for Great Good (tutorial) Yet Another Haskell Tutorial of een nederlandstalige cursustekst ( (S)DT
Bijkomend materiaal In de didactische cluster van de CBA vind je Programming in Haskell Graham Hutton, University of Nottingham Cambridge University Press, January 2007 ( Ook video lectures gebaseerd op het boek!! !!!!! Er is een online manual die je ook tijdens het examen lokaal kan raadplegen: not ( (S)DT
Bijkomend materiaal !!!!! Er is een online manual die je ook tijdens het examen lokaal kan raadplegen: not ( Maar, (Gequoteerde) oefeningen + modeloplossingen (S)DT
5 Haskell Deel 1 Introductie; lijsten in Haskell; lui uitvoeringsmechanisme; polymorfe functies
(S)DT Een eenvoudig Haskell programma fib :: Int -> Int -- een typedeclaratie voor fib fib 0 = 1 fib 1 = 1 fib n = (fib (n-1)) + (fib (n-2)) fib is een recursieve functie die kan toegepast worden op 1 argument dat een integer moet zijn en dan als waarde een integer teruggeeft. zet definitie in een bestand, bijvoorbeeld f.hs laad in in ghci (Haskell platform doe functie-oproepen
(S)DT # GHCi, version : :? for help commentaar tot einde lijn Prelude> :load f -- :load is directief Main> fib 3 -- pas fib toe op resultaat is 3 Main> fib 6 13 Main> fib (-1) ……………………………… computatie gebeurt door.... Functionele taal …. Bouwstenen in je programma’s: herbruikbare functies (die modelleren algoritmen) Expressies in een functionele taal : waarde gebaseerd
(S)DT Main> :type fib fib :: Int -> Int Main> :type (+) (+) :: Num a => a -> a -> a “Voor alle types a die de Num operaties ondersteunen”, (+) is een functie die twee argumenten van het type a afbeeldt op een resultaat van het type a. Int is een voorbeeld van een type dat de Num operaties ondersteunt: de funties (+), (-), (*),...
(S)DT variabele a Main> fib a :1:4: Not in scope: `a‘ Main> fib "asd“ -- de string "asd“ is in feite een lijst van Char -- [Char] -- type fout: [Char] is geen Int :1:0: Couldn't match expected type `Int' against inferred type `[Char]' In the first argument of `fib', namely `"asd"' In the expression: fib "asd" In the definition of `it': it = fib "asd"
(S)DT Haskell: merk op Haskell is getypeerd (statisch en sterk) een functie wordt gedefinieerd aan de hand van verschillende clauses (pattern) matching gebeurt van boven naar onder en eerste argumenten-match wordt genomen er zijn “ingebouwde” functies (+ - ) veranderlijken en functienamen beginnen met kleine letter type begint met een hoofdletter (enkel al Int) functies met letter in prefix (kan anders) speciale tekens in infix
(S)DT Syntactisch alternatieve definitie van fib fib n = if n == 0 then 1 -- else verplicht else if n == 1 then 1 else (fib (n-1)) + (fib (n-2)) Maar fib n = if n == 0 then 1 else if n == 1 then 1 else (fib (n-1)) + (fib (n-2)) Prelude>:l f.hs [1 of 1] Compiling Main ( fib.hs, interpreted ) f.hs:14:0: parse error (possibly incorrect indentation) Failed, modules loaded: none.
(S)DT Andere versies van fib fib :: Int -> Int fib2 :: Int -> Int -> Int -> Int fib n = fib2 n 1 0 fib2 0 n _ = n fib2 i n m = fib2 (i-1) (m+n) n vroeger Main> fib nu Main> fib
Interactieve evaluatie (:set +t) Main> it :: Integer Main> it it :: Integer Main> let x = 3+4 x :: Integer Main> x 7 it :: Integer Main> :show bindings x :: Integer = 7 it :: Integer = 7 (S)DT
(S)DT Haskell: merk verder op waar zijn de typedeclaraties bij sommige vb? het type van een functie die meerdere argumenten kan hebben wordt genoteerd door A1 -> A2 -> A3 -> R
(S)DT Type-declaraties, -inferentie en -checking f a b = g a (h b) -- haakjes zijn nodig g :: R -> S -> T … -- definitie van g h :: Q -> P … -- definitie van h 1. het type van f moet dan zijn: f :: R -> Q -> T 2. is het type van g en h consistent? enkel indien P == S -- beetje te straf Haskell kan types infereren (1) en nakijken (2) gebaseerd op Hindley-Milner type systeem (van ML)
(S)DT Currying en waarom de haakjes? eigenlijk heeft een functie altijd maar 1 argument en is dus een “curried” functie voor een functie f :: A -> B -> C hebben we dat (f a) :: B -> C dus, een functie met n argumenten, toegepast op 1 argument geeft als resultaat een functie met n-1 argumenten bovenstaande declaratie is equivalent met f :: A -> (B -> C) en totaal verschillend van f :: (A -> B) -> C tenslotte, g a h b == (((g a) h) b) en niet g a (h b)
(S)DT Curried versie van de optelling -- versie met tuple als argument add :: (Int,Int) -> Int add (x,y) = x + y -- curried versie cadd cadd :: Int -> Int -> Int cadd x y = x + y -- hetzelfde resultaat maar voor cadd wordt er eerst (cadd x) berekend en dan … -- -> is rechts-associatief dus geen haakjes nodig in cadd :: Int -> (Int -> Int) -- functie-oproepen zijn links-associatief dus geen haakjes nodig in cadd 3 4 want is in feite ((cadd 3) 4) --
Curried functies Meer flexibel dan functies met tuples Partieel toegepaste functies kunnen erg nuttig zijn plus4 = (cadd 4) -- enkele nuttige functies gedefinieerd door sections isNul = (== 0) verdubbel = (2*) tweeTotde = (2^) Een infix operator is in feite ook een curried functie (+) 1 2 (S)DT
(S)DT Operator section partieel toegepaste operator: (+ 3) en (3 +) (op a) b = b op a (a op) b = a op b Main>:t (-3) (-3) :: (Num a) => a
(S)DT Currying, genoemd naar Haskell Curry Logicus die rond functie theorie werkte een simpel theoretisch model: enkel functies met 1 argument, bv. lambda calculus f :: (Int -> Int) -> Int -> Int f g a = g a -- apply g to a Main> f (+3) 4 7 Main> f (3-) 4 Main> f (*4) 4 16 Wat is (f (3+))? Alternatieve def voor f? (3+) $ 4
(S)DT Verschil f :: ( Int -> Int -> Int) -> Int -> Int -> Int f g a b = g a b Main> f (+) Main> f (-) 3 4 Wat is (f (+))? Wat is f?
(S)DT Hogere orde functies functies kunnen als argument een functie hebben functies kunnen als resultaat een functie teruggeven functies kunnen als parameter van andere functies worden gebruikt -- later toepassen van een functie f op 1 argument a noteren we door (f a) eventueel zonder haakjes vergelijk met Lisp (f a b)!!!
(S)DT Geschiedenis : functies als berekeningsmodel, M. Schönfinkel, Haskell Curry en Alonzo Church (lambda calculus) 1950: eerste functionele programmeertaal, Lisp van John McCarthy (emacs) 1978: Backus (Fortran;Backus Naur Form) in zijn Turing Award Lecture: “Can programming be liberated from the Von Neumann style” pleidooi voor echt functioneel programmeren 1980: getypeerde functionele talen programmeertalen: ML, Scheme (een aanpassing van Lisp), Miranda, Gofer, Clean
(S)DT Geschiedenis 1987: groep onderzoekers definieren de taal Haskell Richard Bird, Paul Hudak, John Hughes, Neil Jones, Simon Peyton Jones, Philip Wadler,.... “referential” transparantie “substitute equals for equals”
(S)DT
Prelude The Prelude: a standard module imported by default into all Haskell modules. For more documentation, see the Haskell 98 Report GHC Documentation (Basic) Libraries Prelude, Data.List specificaties + source (S)DT
(S)DT !!!! Haskell.html (en ook oude versie) The Haskell reference is based on Haskell 98 Report and Haskell 98 Libraries Report and a substantial part of descriptions comes from these specifications.
(S)DT Let expressies fib n = if n == 0 then 0 else if n == 1 then 1 else let a = fib (n-1) b = fib (n-2) in a + b a en b zijn lokale variabelen binnen de laatste else tak de = is geen toekenning!!! Maar … let y = a*b f x = (x+y)/y in f c + f d
(S)DT Where clauses fib :: Int -> Int fib n = fib2 n 1 0 where fib2 0 n _ = n fib2 i n m = fib2 (i-1) (m+n) n fib2 is lokaal aan fib (clause die er juist voor staat) fib2 is elders niet zichtbaar fib2 heeft geen type declaratie genest gebruik van syntax mogelijk
(S)DT To do start ergens ghci op type wat kleine programma’s in laad en voer de oproepen uit maak fouten tegen types, indentatie en syntax definieer max :: Int -> Int -> Int max a b en ook maxdrie a b c schrijf minstens 1 nieuwe hogere orde functie ontdek een probleem met fib (2^100)
LIJSTEN IN HASKELL (S)DT
(S)DT Lijsten in Haskell een lijst is een rij zonder constante tijd random toegang en gegeven een lijst kan er in constante tijd een element aan toegevoegd worden (van voor). (anders is het een array) een lijst is leeg of een element gevolgd door een lijst een lijst heeft elementen die alle hetzelfde type hebben.
(S)DT Een lijst van Int een lege lijst [] lijst met elementen [4,5,1] alternatieve notaties voor [4,5,1] 4 : [5,1] 4 : 5 : 1 : [] Prelude> :t (:) (:) :: a -> [a] -> [a] Hoe in Prolog??
(S)DT Lijsten aan elkaar hangen append :: [Int] -> [Int] -> [Int] append [] l = l append (x:xs) a = x : (append xs a) Is er een verschil met het concateren van lijsten in Prolog of linked lists in Java? In Prelude [] ++ ys = ys (x:xs) ++ ys = x : (xs ++ ys) Let op: append x:xs wordt opgevat als (append x) : xs
Herschrijven als uitvoeringsmodel [] ++ ys = ys (x:xs) ++ ys = x : (xs ++ ys) [1,2] ++ [4,5] = (1:2:[]) ++ (4:5:[]) = 1 : ((2:[]) ++ (4:5:[])) = 1 : 2 : ([] ++ (4:5:[])) = 1 : 2 : 4 : 5 : [] (S)DT
(S)DT Let op: vergelijk [2] ++ [3,4,5] 2 ++ [3,4,5] :1:0: No instance for (Num [a]) arising from the literal `2' at :1:0 Possible fix:add an instance declaration for (Num [a]) In the first argument of `(++)', namely `2' In the expression: 2 ++ [3, 4, 6] In the definition of `it': it = 2 ++ [3, 4, 6]
(S)DT Let op: vergelijk 2 : [3,4,5] [2] : [3,4,5] No instance for (Num [t]) arising from the literal `5' at :1:11 Possible fix:add an instance declaration for (Num [t]) In the expression: 5 In the second argument of `(:)', namely `[3, 4, 5]' In the expression: [2] : [3, 4, 5]
(S)DT Lengte van een lijst listlength :: [Int] -> Int listlength [] = 0 listlength (x:xs) = 1 + (listlength xs) of listlength l = ll l 0 where ll [] acc = acc ll (x:xs) acc = ll xs (acc+1) Is er een reden om de ene beter te vinden dan de andere?
(S)DT Insertion sort isort :: [Int] -> [Int] isort [] = [] isort (x:xs) = insert x (isort xs) where insert x [] = [x] insert x (y:ys) = if x < y then (x: y : ys) else (y: (insert x ys))
(S)DT To do schrijf een functie die van een lijst van Ints enkel de positieve getallen behoudt schrijf een functie die de som teruggeeft van de positieve getallen in een lijst van Ints. schrijf een functie die het maximum van een lijst van Ints teruggeeft
LUI UITVOERINGSMECHANSIME (S)DT
(S)DT Parameter passing mechanisme Wat ken je al? Java: call by value actueel argument wordt geëvalueerd voor de methode wordt opgeroepen ?? Haskell: actueel argument wordt geëvalueerd wanneer de waarde ervan nodig is Haskell is lui Haskell heeft call by need
(S)DT Wat betekent lui? g a b = h (a+b) h i = 9 > g 3 4 Waarom ooit de (3+4) uitrekenen als geen enkele functie die waarde ooit zal gebruiken?
(S)DT De essentie van luiheid Een expressie wordt maar gereduceerd tot een waarde indien de waarde nodig is. Een expressie wordt slechts één keer gereduceerd tot een waarde kwadraat i = i * i telop x y = x + y kwadraat (telop 6 2) contrasteer met call by value, call by name
(S)DT Voorbeeld && : luie evaluatie van argumenten (&&), (||) :: Bool -> Bool -> Bool False && x = False True && x = x False || x = x True || x = True Prelude> 4 < 5 && 3 < (5/0) Program error???? Ghci: True -- 3 < Infinitity !! Prelude> 5 < 4 && 3 < (5/0) False
(S)DT Voorbeeld: luie matching f :: [Int] -> [Int] -> Int f [] y = 0 add1 [] = [] f (a:x) [] = 0 add1 (x:xs) = (x+1): add1 xs f (a:x) (b:y) = a + b f (add1 [4,5,6]) (add1 [10,20,30]) = f ((4+1):add1 [5,6]) (add1 [10,20,30]) = f (5:add1 [5,6]) (add1 [10,20,30]) = f (5:add1 [5,6]) (11: add1 [20,30]) = = 16
Wat met accumulator versie van listlength?? listlength l = ll l 0 where ll [] acc = acc ll (x:xs) acc = ll xs (acc+1) Wat gebeurt er met (acc+1)??? De hele som wordt opgebouwd voor ze wordt berekend!! where llstrict [] l = l llstrict (x:xs) l = llstrict xs $! (l+1) -- f $! x behaves as f x, except that the evaluation of the argument x is forced before f is applied (S)DT
(S)DT Luiheid en oneindige lijsten plus1 :: [Int] -> [Int] plus1 [] = [] plus1 (x: xs) = (x+1) : (plus1 xs) langelijst :: [Int] langelijst = (1 : (plus1 langelijst)) -- (*) langelijst is een functie zonder argumenten, enkel resultaat langelijst is een waarde die voldoet aan de vergelijking (*) met call by value is (*) een onmogelijke definitie zolang we maar eindig veel delen van langelijst nodig hebben is er geen probleem: Haskell zal er niet meer berekenen
(S)DT Hoe eindig veel elementen van een oneindige lijst gebruiken? take :: Int -> [Int] -> [Int] take 0 _ = [] take n (x:xs) = x : take (n-1) xs fib :: [Int] fib = (1:1: (f 1 1 )) where f a b = (a+b): (f b (a+b)) Main> take 3 [1,2,3,4,5] [1,2,3] Main> take 10 fib [1,1,2,3,5,8,13,21,34,55] Wat is de waarde van fib ? Welke functie zorgt voor welke evaluatie?
(S)DT Kijk zelf na Main> take 3 langelijst [1,2,3] Welke functie zorgt voor de evaluatie van welke uitdrukking???
isSorted :: [Int] -> Bool isSorted [] = True isSorted [_] = True isSorted (x:y:ys) = (x <= y) && isSorted (y:ys) iterate :: (a -> a) -> a -> [a] iterate next x = x : iterate next (next x) Main> isSorted (iterate (`div` 2) 8) Main> isSorted (iterate (`div` 2) (-100)) Main> iterate (`div` 2) (factorial 100) (S)DT Extra voorbeeld
POLYMORFE FUNCTIES (S)DT
(S)DT Een voorbeeld listlength:: [Int] -> Int listlength [] = 0 listlength (_:xs) = 1 + listlength xs Wat is er aan de clauses van listlength dat maakt dat het alleen zou werken voor [Int] ? Kan het ook werken met elementen van een ander type? een willekeurig type? Niet met die typedeclaratie natuurlijk!!
(S)DT Nu als polymorfe functie listlength:: [t] -> Int listlength [] = 0 listlength (_:xs) = 1 + listlength xs t is een type-veranderlijke !! lees bv. als: voor elk type t is listlength een functie die toegepast op een lijst met type t elementen een Int berekent belangrijk: alle elementen van de lijst moeten van hetzelfde type zijn!!!
(S)DT Bekende functies: polymorfe versie append :: [t] -> [t] -> [t] append [] l = l append (x:xs) a = x : append xs a -- waarom niet [a] -> [b] -> [c] ??? take :: Int -> [t] -> [t] take 0 _ = [] take n (x:xs) = x : take (n-1) xs
(S)DT Nieuwe polymorfe functies id :: a -> a -- identiteits-functie id x = x map :: (a -> b) -> [a] -> [b] map f [] = [] map f (h: r) = (f h) : map f r Main> map (+1) [1,2,3] [2,3,4] g i = if i > 0 then True else False > map g [-1,3,-4,4] meer dan 1 type parameter!!! soms a == b
(S)DT Tuples lijsten versmelten en een nieuw data type zip :: [a] -> [b] -> [(a,b)] zip []_ = [] zip _ [] = [] zip (x:xs) (y:ys) = ( (x,y) : (zip xs ys)) Main> zip [1,2,3,4] [True,False,True] [ (1,True), (2,False), (3,True)] tuples van elke grootte zijn ingebouwd en pattern matchen enkel met tuples van gelijke grootte
(S)DT Guards als syntactische suiker merge :: [Int] -> [Int] -> [Int] merge [] l = l merge l [] = l merge (x:xs) (y:ys) = if (x < y) then (x : merge xs (y:ys)) else if x > y then (y : merge (x:xs) ys) else (x : merge xs ys) -- of ook : laatste clause met guards merge (x:xs) (y:ys) | x < y = (x : merge xs (y:ys)) | x > y = (y : merge (x:xs) ys) | True = (x : merge xs ys)
(S)DT Verschillende stijlen in Haskell: declaratie expressie een functie-definitie is een opeenvolging van onafhankelijke gelijkheden sign x | x>0 = 1 | x==0 = 0 | x<0 = -1 een functie-definitie is een expressie sign x = if x>0 then 1 else if x==0 then 0 else -1
(S)DT Schrijf nu zelf een functie quicksort voor [Int] een functie die een oneindige lijst van alle positieve oneven getallen specifieert een functie die een oneindige lijst van alle priemgetallen specifieert een functie die nagaat of een gegeven getal een priemgetal is probeer een polymorfe sort te schrijven
(S)DT Nog eens: luie evaluatie Actueel argument van een functie: uitgestelde berekening Werkt met een closure: te evalueren expressie + relevante bindingen Closures nemen plaats in op de heap (ook ints, lists, …) Call by need en call by value: zelfde antwoord als het programma eindigt MAAR nooit meer reductie-stappen want.. Soms toch waarde berekenen: wanneer?
(S)DT Stricte functies Argument van een functie: als de waarde ervan altijd nodig is, dan is de functie strict in dat argument. Voorbeeld: fact n, kwadraad i, telop x y Maar project x y = x constPi x = 3.14 Voor stricte argumenten: compiler kan beslissen toch uit te rekenen Voor niet-stricte argumenten: (keten van) closures nemen plaats op heap
(S)DT Nut van oneindige lijsten? lui? Programma: abstracter, eenvoudiger Producer + consumer toepassingen met corouting Een tijdelijke lijst wordt “on demand” opgebouwd Enkel berekenen wat nodig is voor het vinden van een oplossing
(S)DT Wat met andere functionele talen?? Stricte talen (call by value): Lisp, Scheme, ML, Erlang (concurrent) Niet-stricte/luie talen (call by need): Miranda (inspiratie voor Haskell), Hope, Clean (concurrent) Wat is het beste?? Implicaties van luiheid? Puur functioneel!!
(S)DT De lambda notatie Een functie hoeft geen naam te hebben Enkel een voorschrift om de functie te evalueren Mogelijk in Haskell met de λ-notatie voor functies plus1list = map plus1 where plus1 i = i ofwel ook plus1list = map (\x -> x+1) Meer algemeen (\a b c d -> (fib a) + (head b) * c +d ) Wat is (\x y -> x * y) 3 ?
(S)DT filter :: (t -> Bool) -> [t] -> [t] filter p [] = [] filter p (x:xs) | p x = x : rest | otherwise = rest where rest = filter p xs Miranda Lisp, ML, Scheme filter = \p -> \xs -> case xs of [] -> [] (x:xs) -> let rest = filter p xs in if (p x) then x : rest else rest
(S)DT Lijsten van gehele getallen Alsof Haskell vooraf definieerde: [n..m] = if n > m then [] else n : [ (n+1).. m] [i..] = (i : [(i+1).. ]) dus Main> [3..8] [3,4,5,6,7,8] Main> [-5..1] [-5,-4,-3,-2,-1,0,1] Main> take 4 [21..] [21,22,23,24]
(S)DT Functie samenstelling filterpos::[Int] -> [Int] filterpos [] = [] filterpos (x:xs) | x > 0 = x : filterpos xs | otherwise = filterpos xs suml :: [Int] -> Int suml [] = 0 suml (x:xs) = x + suml xs addpos l = suml (filterpos l) addpos2 = suml. filterpos -- suml na filterpos addpos3 = suml. (filter (>0)) addpos4 = foldr (+) 0. filter (>0)
Functie samenstelling Prelude> :t (.) (.) :: (b -> c) -> (a -> b) -> a -> c (f.g) x = f ( g x) foldr :: (a->b->b) -> b -> [a] -> b it takes the second argument and the last item of the list and applies the function, then it takes the penultimate item from the end and the result, and so on. See scanr for intermediate results.scanr Input: foldr (+) 5 [1,2,3,4] Output: (1 + ( 2 + (3 + (4 +5)))) Input: scanr (+) 5 [1,2,3,4] Output: [15,14,12,9,5] (S)DT
(S)DT Haskell: einde deel 1 wordt vervolgd….