(S)DT Haskell Deel 2 list comprehensions, types en type klassen, programma transformatie, I/O
LIST COMPREHENSIONS (S)DT
3 List comprehension Main> [ 2*a | a <- [2,3,8] ] [4,6,16] lezen we als de lijst met waarden 2*a met a geselecteerd uit de lijst [2,3,8] -- <- generator l = take 10 [ x*x | x <- (odds [1..])] where odds [] = [] odds (x:xs) | x `mod` 2 == 1 = (x : odds xs) | True = (odds xs) Main> l [1,9,25,49,81,121,169,225,289,361]
(S)DT Nog list comprehensions Main> let l = [2,4,7] in [ a > 4 | a <-l] [False,False,True] Main> let l = [8,2,4,7] in [a | a 4] [8,7] -- generator en test Main> take 10 [ x*x | x <- [1..], x `mod` 2 == 1] [1,9,25,49,81,121,169,225,289,361] Main> let l= [ [], [1], [4,5], []] in [ a | (a:x) <-l] [1,4] Main> let l = [ (1,2), (4,3)] in [ a+b| (a,b) b] [7]
(S)DT Variabelen in list comprehensions Main> let l = [(1,2), (4,3)] in [b | (1,b) <- l] [2] Main> let l = [(1,2), (3,3)] in [b | (b,b) <- l] ERROR - Repeated variable "b" in pattern -- scope van variabelen f x l = [ a | (x,a) <- l] -- nieuwe x g x l = [ a | (y,a) <- l, x == y] Main> f 1 [(1,2), (4,3)] [2,3] Main> g 1 [(1,2), (4,3)] [2]
(S)DT Verschillende generatoren Main> [ (a,b) | a <- [1..2], b <- [1..3]] [(1,1),(1,2),(1,3),(2,1),(2,2),(2,3)] Main> [ (a,b) | b <- [1..3], a <- [1,2]] [(1,1),(2,1),(1,2),(2,2),(1,3),(2,3)] Main> let l = [(2,7), (4,3)] -- b is afhankelijk in [(a,b)| (a,_) <- l, b <- [1..a]] [(2,1),(2,2),(4,1),(4,2),(4,3),(4,4)] -- Een Pythagorees drietal (a, b, c) bestaat uit drie positieve gehele getallen a, b, c met a < b < c waarvoor geldt a 2 + b 2 = c 2positievegehele getallen
(S)DT Algemene vorm [ e | q1, q2, …, qn] qi is (pattern <- listexpr) of (boolean expr) vars in qi moeten links ervan voorkomen elke var in een pattern is nieuw! Schrijf map f (filter p xs) nu met list comprehension En eventueel ook qsort
(S)DT Opnieuw priem priemgetallen n = [ x | x <- [1..n], priem x] priem x = factors x == [1,x] factors n = [ x | x <- [1..n], n `mod` x == 0]
TYPES Data declaraties; Polymorfe types; Type klassen (S)DT
10 Welke types kennen we? Int, Integer [Int] (Bool, Int) type variabele t,a,b lengthlist :: [t] -> Int map :: (a->b) -> [a] -> [b] Char ‘a’ String “hallo world” in feite synoniem voor lijst van Char type String = [Char] type Positie = (Int,Int)
(S)DT Data declaratie voor type IBoom data IBoom = Knoop IBoom IBoom | Blad Int -- wat zijn de mogelijke waarden van het type IBoom? -- Knoop (Blad 4) (Knoop (Blad 3) (Blad 100)) balanced :: IBoom -> Bool balanced (Knoop left right) = let l = diepte left r = diepte right in (ok l r ) && (balanced left) && (balanced right) balanced (Blad _) = True ok :: Int -> Int -> Bool ok l r = (l==r) || (l == (r+1)) || ( (l+1) == r)
(S)DT diepte :: IBoom -> Int diepte (Knoop l r) = 1 + maxi (diepte l) (diepte r) diepte (Blad _) = 1 maxi :: Int -> Int -> Int maxi x y | x > y = x | otherwise = y
(S)DT Ingebouwde types data Bool = True | False -- enumeratie -- Bool is een nieuw type met 2 mogelijke waarden -- constructor-functies: True en False data Int = 0 | 1 | -1 | 2 | -2 | 3 …
(S)DT Meer types Record type data Punt = Coord Int Int -- Coord als data constructor data Punt = Punt Int Int -- Verschil?? data Punt = MkPunt Int Int -- Verschil?? Hoe: Int of Bool Constructors worden gebruikt: Als een functie om waarden aan te maken (in rechter deel van equations) In patronen om waarden af te breken (in linker deel van equations)
(S)DT Polymorfe types data Boom a =Knoop (Boom a) (Boom a)|Blad a Voor elk type a is (Boom a) een data type Boom is een typeconstructor Knoop is een dataconstructor – of een constructor functie Main> :t Knoop Knoop :: Boom a -> Boom a -> Boom a Main> :t Blad Blad :: a -> Boom a
(S)DT Bladeren bladeren :: Boom a -> [a] bladeren (Knoop l r) = bladeren l ++ bladeren r bladeren (Blad b) = [b] -- Nog een ingebouwd (polymorf) datatype … data [a] = [] | a : [a]
(S)DT Werkvoorbeeld polymorfisme isort :: [Int] -> [Int] isort [] = [] isort (x:xs) = insert x (isort xs) insert :: Int -> [Int] -> [Int] insert x [] = [x] insert x (y:ys) | x<y = x:y:ys | otherwise = y : (insert x ys) -- polymorfe versie?? door ??? -- Int vervangen door ???
(S)DT Polymorfe isort isort :: (t -> t -> Bool) -> [t] -> [t] isort _ [] = [] -- was isort (x:xs) = insert x (isort xs) isort orde (x:xs) = insert orde x (isort orde xs) insert :: (t -> t -> Bool) -> t -> [t] -> [t] insert _ x [] = [x] insert orde x (y:ys) | (orde x y) = x:y:ys | otherwise = y : (insert orde x ys) Main> isort (<) [3,1,7] [1,3,7]
(S)DT Evaluatie: wat gedaan? extra parameter (de functie) doorgeven – eventueel diep de type declaraties aanpassen code aanpassen de waarde van die parameter is gekend op moment van de topoproep de waarde (naam van de functie) kan afhankelijk zijn van het type Misschien is het handiger om uit te drukken: isort mag enkel op een lijst met elementen waarop (<) gedefineerd is
(S)DT Overloading wat is overloading?? ad-hoc polymorfisme voorbeeld : een zelfde operator + kan gebruikt worden voor verschillende types van operanden (Int, Float) en de definitie van + is type-afhankelijk dezelfde naam maar verschillende operaties op verschillende types wat is het type van square n = n*n ?
(S)DT Type classes: uitbreidbare overloading class HeeftOrde a where -- klasse-declaratie ( a -> Bool isort :: (HeeftOrde t) => [t] -> [t] -- klasse-constraint isort [] = [] isort (x:xs) = insert x (isort xs) insert :: (HeeftOrde t) => t -> [t] -> [t] insert x [] = [x] insert x (y:ys) | x <<< y = x:y:ys | otherwise = y: (insert x ys)
(S)DT Klasse-constraint insert :: (HeeftOrde t) => t -> [t] -> [t] insert x [] = [x] insert x (y:ys) | x <<< y = x:y:ys | otherwise = y: (insert x ys) zonder klasse-constraint zegt Haskell: ERROR "cl.hs":12 - Cannot justify constraints in explicitly typed binding *** Expression : insert *** Type : a -> [a] -> [a] *** Constraints : HeeftOrde a Main> isort [2,1] ERROR - Unresolved overloading *** Type : (Num a, HeeftOrde a) => [a] *** Expression : isort [2,1]
(S)DT Instance van een klasse data Dagen = Maan | Dins | Woen | Dond |Vrij instance HeeftOrde Dagen where Maan <<< _ = True _ <<< Maan = False Dins <<< _ = True _ <<< Dins = False Woen <<< _ = True _ <<< Woen = False Dond <<< _ = True _ <<< _ = False Main> isort [Maan, Woen, Dins] [Maan,Dins,Woen]
(S)DT Dus nu met type classes geen extra parameters geen code aanpassen type declaratie aanpassen – eventueel diep het type van de polymorfe parameter is gekend op moment van de topoproep de compiler kan achter de schermen de vergelijkingsfunctie meegeven (en doet dat) de naam van de functie is voor alle instances van de klasse dezelfde. Gelijkenis met interface van Java?
(S)DT Wat hebben we? Een type klasse is een groep van types (instances) waarop bepaalde functies kunnen worden toegepast (vergelijk met polymorf type) Type klassen laten toe om de parameters van de polymorfe types/functies verder te beperken Bv. type parameter a, maar == gedefinieerd voor a Type klassen en instanties worden gebruikt voor het checken van de types tijdens compilatie. Type klassen hiërarchie: class (Eq a) => Ord a where … -- Ord is een subklasse, erft van Eq
(S)DT Dus Een type klasse beschrijft een “concept” Een type kan een instance zijn van een type klasse en is dan een implementatie van het “concept”. Generische programma’s mogelijk dankzij Parametrisch polymorfisme Type constraints (zie context) In Java, concept als “interface” En een Java class implements interface
(S)DT Ingebouwde klassen uit Prelude class Eq a where (==), (/=) :: a -> a -> Bool x /= y = not (x==y) -- default definitie class (Eq a) => Ord a where -- class extension compare :: a -> a -> Ordering ( =), (>) :: a -> a -> Bool compare x y | x == y = EQ | x <= y = LT | otherwise = GT x <= y = compare x y /= GT x < y = compare x y == LT
(S)DT Ingebouwde instanties data Bool = False | True instance Eq Bool where True == True = True False == False = True _ == _ = False instance Ord Bool where False <= True = True _ <= _ = False
(S)DT Eenvoudig instanties maken Er is dikwijls een natuurlijke implementatie voor de functies in een klasse data Bool = False | True deriving (Eq, Ord, Show, … ) Is elk type een instance van Eq? Door er deriving Eq bij te zetten? Waarom mag dezelfde variabele niet tweemaal in een pattern match?
(S)DT De klasse Num class (Eq a, Show a) => Num a where (+), (-), (*) :: a -> a -> a negate :: a -> a abs, signum :: a -> a fromInteger :: Integer -> a fromInt :: Int -> a x - y = x + negate y fromInt = fromIntegral negate x = 0 - x Main> :t square square :: Num a => a -> a
(S)DT Int als instance van Num -- Int is builtin datatype of fixed size integers primitive primEqInt :: Int -> Int -> Bool instance Eq Int where (==) = primEqInt instance Num Int where (+) = primPlusInt -- ingebouwd prim… (-) = primMinusInt negate = primNegInt fromInteger n = n
(S)DT Type klassen en type inferentie De type inferentie leidt het meest algemene type af. Daarin komen dikwijls type constraints voor (+) :: Num a => a -> a -> a (/) :: Fractional a => a -> a -> a
(S)DT Instances met voorwaarden -- data [a] = [] | a : [a] deriving (Eq, Ord) instance Eq a => Eq [a] where -- context!! [] == [] = True (x:xs) == (y:ys) = x == y && xs == ys -- welke? _ == _ = False
(S)DT de klasse Text ERROR - Cannot find "show" function for: *** Expression : isort [Maan,Woen,Dins] *** Of type : [Dagen] class Show a where show :: a -> String data Dagen = Maan | Dins | Woen | Dond |Vrij deriving Show Main> show Maan “Maan”
(S)DT Enkele vragen Wat is het meest algemene type van elem 1 [ 2, 4, 9] False elem ‘a’ “Harry” True En van allevk a [] = [] allevk a ((x,y): rest) = if a == x then y : allvk a rest else allevk a rest En van qsort ??
(S)DT Nog enkele vragen Herinner je het type Boom a data Boom a = Knoop (Boom a) (Boom a)| Blad a Hoe een gelijkheidstest definieren? Type declaratie van de functie element?? Eindige set (wat is een set??) voorgesteld als een lijst Type declaratie Voegtoe, unie, element? Gelijkheid van 2 sets?
Meer info over classes In TasteofHaskell.pdf : slides (S)DT
PROGRAMMA TRANSFORMATIE Equational reasoning (S)DT
(S)DT reverse en reverse nrev [] = [] nrev (x:xs) = nrev xs ++ [x] [] ++ l = l (x:xs) ++ l = (x : (xs ++ l)) reverse l = rev2 l [] rev2 [] a = a -- (a) rev2 (x:xs) a = rev2 xs (x:a) -- (b) nrev is O( n**2) – reverse is O(n)
(S)DT Automatisch van nrev naar reverse vertrek van de invariant voor alle l : rev2 l a = (nrev l) ++ a gebaseerd op het type van l, herschrijf tot: voor l== [] of l==(x:xs): rev2 l a = (nrev l) ++ a of zonder quantoren 1. rev2 [] a = (nrev [] ) ++ a 2. rev2 (x:xs) a = (nrev (x:xs)) ++ a we bewijzen achtereen volgens (a) en (b)
(S)DT Te bewijzen: rev2 [] a = a rev2 [] a = (nrev []) ++ a -- (1) = [] ++ a -- gebruik 1ste regel nrev = a -- 1ste regel van ++ We hebben (a) bewezen en gebruikten de invariant en partiële evaluatie
(S)DT Te bewijzen rev2 (x:xs) a = rev2 xs (x:a) rev2 (x:xs) a = (nrev (x:xs)) ++ a -- (2) = ( (nrev xs) ++ [x]) ++ a -- gebruik 2e regel nrev = (nrev xs) ++ ([x] ++ a) is associatief = (nrev xs) ++ (x : a) -- regels van ++ = rev2 xs (x:a) -- invariant omgekeerd We hebben (b) bewezen en gebruikten de invariant en partiële evaluatie en de associativiteit van ++ (nog te bewijzen)
(S)DT Te bewijzen (a ++ b) ++ c = a ++ (b ++ c) Inductie op lengte van a: a kan [] zijn of (x:xs) Basis van de inductie: a = [] ([] ++ b ) ++ c = b ++ c -- regel 1 van ++ Inductiestap: stel associativiteit is waar voor a == xs, bewijs dat associatviteit is waar voor a = (x:xs)
(S)DT Associativiteit: inductiestap x:xs 2e regel : (x:xs) ++ l = (x : (xs ++ l)) (a ++ b ) ++ c = ( (x:xs) ++ b) ++ c -- a == (x:xs) = ( x : (xs ++ b) ) ++ c -- regel 2 van ++ = x : ( (xs ++ b) ++ c) -- regel 2 van ++ = x : ( xs ++ (b ++ c)) -- asso. Door inductie = (x : xs) ++ (b ++ c) -- omgekeerde regel 2 ++ = a ++ (b ++ c) -- a == (x:xs)
I/O HASKELL (S)DT
(S)DT I/O in pure Haskell heeft geen zin want referential transparency: 2x oproepen, 2x zelfde resultaat luiheid: op welk ogenblik wordt een functie uitgevoerd? Hoe zinvol aan I/O doen in Haskell? de toestand van de wereld is een impliciet argument van functies die I/O doen sequentialisatie van I/O acties
(S)DT I/O ondersteuning door type systeem en nieuwe syntactische constructie – do getChar :: IO Char putChar :: Char -> IO () Een waarde van type IO t is een “action” die, als ze wordt uitgevoerd, mogelijks wat I/O doet voor een resultaat van type t te geven. Dus wat leiden we af uit het type van putChar ?
Achterliggend idee Een waarde van type IO t is een “action” die, als ze wordt uitgevoerd, mogelijks wat I/O doet voor een resultaat van type t te geven. type IO t = World -> (t,World) -- een benadering!!! (S)DT World inWorld out I/O t result :: t
(S)DT Voorbeeld I/O en do-notatie Een functie die een Int als argument heeft, IO doet en een string teruggeeft: n chars worden gelezen en omgekeerd als string teruggegeven foo :: Int -> IO String foo n = do a <- getnchars n [] return a getnchars :: Int -> String -> IO String getnchars 0 l = return l getnchars (n+1) l = do a <- getChar s <- getnchars n (a:l) return s -- return :: a -> IO a -- do maakt 1 actie van een sequentie van acties
(S)DT Een functie f doet IO f heeft als return type IO iets als g f oproept, dan doet g IO en heeft type IO iets’ f roept minstens 1 functie op die IO doet en kan functies oproepen die dat niet doen waarde van iets kan opgevangen worden door de constructie <- do dient om IO te sequentialiseren
(S)DT Voorbeeld opnieuw foo :: Int -> IO String foo n = do a <- getnchars n return (rev a) getnchars :: Int -> IO String getnchars 0 = return [] getnchars (n+1) = do a <- getChar s <- getnchars n return (a:s)
(S)DT Waarden van het type (IO t) zijn first class forever :: IO () -> IO () forever a = do { a; forever a } repeatN :: Int -> IO () -> IO () repeatN 0 a = return () repeatN n a = do { a; repeatN (n-1) a } Main > repeatN 10 (putChar ‘x’)
(S)DT Lui en I/O … vergelijk f = let x = (error) in 3 g = do x <- getnchars (error) [] return 3 Main> f 3 Main> g Program error: error
(S)DT N-queens interactief qs :: IO () qs = do i <- readi schrijf (queens i) schrijf :: Show a => [a] -> IO () schrijf [] = return () schrijf (x : xs) = do putStr “\n” c <- putStr (show x) schrijf xs -- Main> :t show -- show :: Show a => a -> String -- Main> :t () trivial type -- () :: ()
(S)DT readi :: IO Int readi = riacc 0 riacc :: Int -> IO Int riacc acc = do x <- getChar if ( (tr x) < 0) then return acc else do z <- (riacc ((10* acc) + (tr x))) return z tr ‘0’ = 0 … tr ‘9’ = 9 tr _ = -1
(S)DT Main> qs 4 [3,1,4,2] [2,4,1,3] Main> qs 6 [5,3,1,6,4,2] [4,1,5,2,6,3] [3,6,2,5,1,4] [2,4,6,1,3,5]
Doing “timings” import CPUTime main :: IO Int main = do t1 <- getCPUTime let f1 = fib(21) print f1 -- forceert de oproep van fib t2 <- getCPUTime print "uitvoeringstijd : " print (t2-t1) -- in picoseconden (10 -12) return f1 {- oproep Main> do { a<- main; print a } "uitvoeringstijd : " -} (S)DT
(S)DT De klasse Show data Itree = Leeg | Node Itree Int Itree instance Show Itree where -- showsPrec :: Int -> a -> (String -> String) showsPrec n Leeg = ([]++) showsPrec n (Node t1 i t2) = let n1 = n+1 ni = n+i in (showsPrec n1 t1).((show ni)++).(showsPrec n1 t2) -- show x = showsPrec 0 x “” Main> show (Node (Node Leeg 23 Leeg) 5 Leeg) “245”
(S)DT In feite: I/O Monad Haskell maakt gebruik van monads om berekeningen met waarden te structureren: bijvoorbeeld te sequentialiseren. I/O is in feite ook zo’n monad en de do- syntax kan gebruikt worden voor monads. Nog een monad: berekening die kan falen data Maybe a = Nothing | Just a In Prelude class Monad m where …
(S)DT Haskell: einde deel 2