Kapitel 3: Eine einfache Programmiersprache Programmieren in Haskell 1 Datentypen, Datentypdefinitionen data Instrument = | | | Oboe HonkyTonkPiano Cello VoiceAahs data Musik Note Ton Dauer Pause Dauer Musik :*: Musik Musik :+: Musik Instr Instrument Musik Tempo GanzeZahl Musik Programmieren in Haskell = | | | | | 2 Allgemeine Form data T a1 . . . am Programmieren in Haskell = C1 t11 . . . t1n1 | ... | Cr tr1 . . . trnr 3 Wahrheitswerte data Bool = False | True Programmieren in Haskell -- vordefiniert 4 Wahrheitswerte data Bool = False | True -- vordefiniert not :: Bool -> Bool -- vordefiniert Programmieren in Haskell 4 Wahrheitswerte data Bool = False | True -- vordefiniert not :: Bool -> Bool not True = False not False = True -- vordefiniert Programmieren in Haskell 4 Wahrheitswerte data Bool = False | True -- vordefiniert not :: Bool -> Bool not True = False not False = True -- vordefiniert (&&), (||) :: Bool -> Bool -> Bool -- vordefiniert Programmieren in Haskell 4 Wahrheitswerte data Bool = False | True -- vordefiniert not :: Bool -> Bool not True = False not False = True -- vordefiniert (&&), (||) :: Bool -> Bool -> Bool False && y = False True && y = y -- vordefiniert Programmieren in Haskell 4 Wahrheitswerte data Bool = False | True -- vordefiniert not :: Bool -> Bool not True = False not False = True -- vordefiniert (&&), False True True False -- vordefiniert (||) && y && y || y || y Programmieren in Haskell :: Bool -> Bool -> Bool = False = y = True = y 4 Wahrheitswerte data Bool = False | True -- vordefiniert not :: Bool -> Bool not True = False not False = True -- vordefiniert (&&), False True True False -- vordefiniert (||) && y && y || y || y :: Bool -> Bool -> Bool = False = y = True = y ifThenElse :: Bool -> a -> a -> a ifThenElse True a a’ = a ifThenElse False a a’ = a’ Mixfix-Notation: if x >= 0 then x else -x Programmieren in Haskell 4 Ganze Zahlen data Ganz = Zero | Succ Ganz Zero, Succ Zero, Succ (Succ Zero), . . . Eingebaute Datentypen: Int und Integer Operatoren: (+), (-), (*), div, mod, (^) Programmieren in Haskell 5 Tupeltypen data Pair a b = Pair a b data Triple a b c = Triple a b c Abkürzende Schreibweise: (x,y), (x,y,z) Selektorfunktionen: fst :: (a,b) -> a fst (a,b) = a -- vordefiniert snd :: (a,b) -> b snd (a,b) = b -- vordefiniert Programmieren in Haskell 6 Listen data List a = Empty | Front a (List a) Eingebaute Listen: [a] statt List a, [] statt Empty , (x:xs) statt Front x xs Abkürzende Schreibweise: [x,y,z] statt (x:y:z:[]) Programmieren in Haskell 7 Funktionen auf Listen (++) :: [a] -> [a] -> [a] [] ++ bs = bs (a:as) ++ bs = a:(as++bs) head :: [a] -> a head (a:as) = a tail :: [a] -> [a] tail (a:as) = as reverse :: [a] -> [a] reverse [] = [] reverse (a:as)= reverse as ++ [a] Programmieren in Haskell 8 Der Typ Maybe data Maybe a = Nothing | Just a -- vordefiniert minimum1 :: [Integer] -> Integer minimum1 [a] = a minimum1 (a:as) = min a (minimum1 as) minimum0 :: [Integer] -> Maybe Integer minimum0 [] = Nothing minimum0 (a:as) = Just (minimum1 (a:as)) Programmieren in Haskell 9 Zeichen und Zeichenketten data Char = ... | ’0’ | ’1’ ... | ... | ’A’ | ’B’ ... | ... | ’a’ | ’b’ ... -- Pseudo-Haskell type String = [Char] Abkürzende Schreibweise: "Marvin" statt [’M’,’a’,’r’,’v’,’i’,’n’], "" statt [] isSpace :: Char -> Bool isSpace c = c == ’ ’ || c == ’\t’ || c == ’\n’ || c == ’\r’ || c == ’\f’ || c == ’\v’ Programmieren in Haskell 10 Typsynonyme Schon gesehen: Ton, Dauer, DNA, Protein, String Nutzen und Gefahren: type OrdList a = [a] merge :: OrdList a -> OrdList a -> OrdList a merge xs ys = xs ++ ys Typ ok? Programmieren in Haskell 11 Typsynonyme Schon gesehen: Ton, Dauer, DNA, Protein, String Nutzen und Gefahren: type OrdList a = [a] merge :: OrdList a -> OrdList a -> OrdList a merge xs ys = xs ++ ys Typ ok? Ja Programmieren in Haskell 11 Typsynonyme Schon gesehen: Ton, Dauer, DNA, Protein, String Nutzen und Gefahren: type OrdList a = [a] merge :: OrdList a -> OrdList a -> OrdList a merge xs ys = xs ++ ys Typ ok? Ja Ergebnis geordnete Liste? Programmieren in Haskell 11 Typsynonyme Schon gesehen: Ton, Dauer, DNA, Protein, String Nutzen und Gefahren: type OrdList a = [a] merge :: OrdList a -> OrdList a -> OrdList a merge xs ys = xs ++ ys Typ ok? Ja Ergebnis geordnete Liste? Nein Programmieren in Haskell 11 Typdeklarationen, Typprüfung und Typinferenz map f [] = [] map f (a:as) = f a : map f as -- vordefiniert Beobachtungen über map Typ von map map hat zwei Argumente. c -> d -> e Das Ergebnis ist eine Liste. c -> d -> [b] Das zweite Argument ist eine Liste. c -> [a] -> [b] Das erste Argument ist eine Funktion, (f -> g) -> [a] -> [b] die Argumente vom Typ a erhält, (a -> g) -> [a] -> [b] und Elemente der Ergebnisliste vom Typ b liefert. (a -> b) -> [a] -> [b] map :: (a -> b) -> [a] -> [b] Programmieren in Haskell 12 Spezialisierungen map :: (a -> b) -> [a] -> [b] Spezialisierung von Anwendung von map a b map c’ [1/4, 1/8, 1/8, 1/4] Dauer Musik Musik Musik map genCode cs Codon AminoAcid map (map translate) orfs zwei Auftreten verschiedenen Typs! map (transponiere 5) [cDurTonika, cDurSkala, bruderJakob] inneres Auftreten Codon Protein äußeres Auftreten [[Codon]] [Protein] Programmieren in Haskell 13 Typklassen und Typkontexte • Eq enthält alle Typen, deren Elemente man auf Gleichheit testen kann, und definiert die folgenden Funktionen: (==) :: (Eq a) => a -> a -> Bool (/=) :: (Eq a) => a -> a -> Bool • Ord enthält alle Typen, deren Elemente man bezüglich einer Ordnungsrelation vergleichen kann, und definiert u.a. die folgenden Funktionen: (<), (<=), (>=), (>) :: (Ord a) => a -> a -> Bool max, min :: (Ord a) => a -> a -> a Programmieren in Haskell 14 • Num enthält alle numerischen Typen und definiert die grundlegenden arithmetischen Operationen. (+), (-), (*) :: (Num a) => a -> a -> a negate :: (Num a) => a -> a Die Funktion negate entspricht dem unären Minus. • Integral enthält die ganzzahligen Typen Int und Integer und definiert u.a. die folgenden Funktionen: div, mod :: (Integral a) => a -> a -> a even, odd :: (Integral a) => a -> Bool • Show enthält alle Typen, die eine externe Darstellung als Zeichenketten haben. Die Funktion show liefert diese Darstellung. show :: (Show a) => a -> String Programmieren in Haskell 15 minimum1 :: [Integer] -> Integer minimum1 [a] = a minimum1 (a:as) = min a (minimum1 as) minimum :: (Ord a) => [a] -> a minimum [a] = a minimum (a:as) = min a (minimum as) -- vordefiniert min a b = if a < b then a else b Programmieren in Haskell 16 Wertdefinitionen, Variablenbindungen theFinalAnswer :: Integer theFinalAnswer = 42 aShortList :: [Integer] aShortList = [1,2,3] helloWorld :: String helloWorld = "Hello World" Programmieren in Haskell 17 theFinalAnswer’ :: Integer theFinalAnswer’ = 6*7 aShortList’ aShortList’ helloWorld’ helloWorld’ :: [Integer] = reverse ([3]++[2,1]) :: String = "Hello" ++ " " ++ "World" monotonie :: Musik monotonie = c’ (1/1) :*: monotonie Programmieren in Haskell 18 Musterbindungen Tempo t (Instr i (m1 :+: m2 :+: m3:+: m4)) = bruderJakob Allgemein: p = e, mit p Muster, e Ausdruck Programmieren in Haskell 19 Funktionsbindungen length :: [a] -> Int length [] = 0 length (a:as) = 1 + length as Programmieren in Haskell -- vordefiniert 20 Allgemeiner Fall f p11 . . . p1n = e1 ... = ... f pk1 . . . pkn = ek pij : Muster, ei : Ausdrücke Programmieren in Haskell 21 Bewachte Gleichungen dropSpaces :: String -> String dropSpaces [] = [] dropSpaces (c:cs) | isSpace c = dropSpaces cs | otherwise = c : dropSpaces cs squeeze :: String -> String squeeze [] = [] squeeze (c:c’:cs) | isSpace c && isSpace c’ = squeeze (c’:cs) squeeze (c:cs) = c : squeeze cs Programmieren in Haskell 22 member :: (Ord a) member a [] = member a (b:bs) | a < b = | a == b = | a > b = Programmieren in Haskell => a -> OrdList a -> Bool False False True member a bs 23 Gleichungen mit lokalen Definitionen power :: (Num a, Integral b) => a -> b -> a power x n | n == 0 = 1 | n ‘mod‘ 2 == 0 = y | otherwise = y*x where y = power (x*x) (n ‘div‘ 2) Programmieren in Haskell 24 Gleichungen mit lokalen Definitionen power :: (Num a, Integral b) => a -> b -> a power x n | n == 0 = 1 | n ‘mod‘ 2 == 0 = y | otherwise = y*x where y = power (x*x) (n ‘div‘ 2) pow :: (Num a) => a -> Integer -> a pow x n | n == 0 = 1 | otherwise = x * pow x (n-1) Programmieren in Haskell 24 Gleichungen mit lokalen Definitionen power :: (Num a, Integral b) => a -> b -> a power x n | n == 0 = 1 | n ‘mod‘ 2 == 0 = y | otherwise = y*x where y = power (x*x) (n ‘div‘ 2) pow :: (Num a) => a -> Integer -> a pow x n | n == 0 = 1 | otherwise = x * pow x (n-1) xn = x2(n div 2)+n mod 2 = (x2 )n div 2 xn mod 2 Programmieren in Haskell 24 Lokale Musterbindung splitWord :: String -> (String, String) splitWord [] = ([],[]) splitWord (c:cs) | isSpace c = ([], c:cs) | otherwise = (c:w, cs’) where (w,cs’) = splitWord cs Programmieren in Haskell 25 Gültigkeits- oder Sichtbarkeitsbereiche f :: Int -> Int f x = f (x+x) where x = 1 f x = x Programmieren in Haskell -- Negativ-Beispiel 26 Gültigkeits- oder Sichtbarkeitsbereiche f :: Int -> Int f x = f (x+x) where x = 1 f x = x -- Negativ-Beispiel f1 :: Int -> Int f1 x1 = f2 (x2+x2) where x2 = 1 f2 x3 = x3 Programmieren in Haskell 26 Abseitsregel • Das erste Zeichen der Definition unmittelbar nach dem where bestimmt die Einrücktiefe des neu eröffneten Definitionsblocks. • Zeilen, die weiter als bis zu dieser Position eingerückt sind, gelten als Fortsetzungen einer begonnenen lokalen Definition. • Zeilen auf gleicher Einrücktiefe beginnen eine neue Definition im lokalen Block. • Die erste geringer eingerückte Zeile beendet den lokalen Block. Programmieren in Haskell 27 Die allgemeine Form von Gleichungen f p11 . . . p1n m1 pk1 . . . pkn mk ... f Die pij sind beliebige Muster und die mi nehmen wahlweise eine der beiden folgenden Formen an: = e where { d1 ; . . . ;dp } oder | g1 = e1 ... | gq = eq where { d1 ; . . . ;dp } Programmieren in Haskell 28 Das Rechnen mit Gleichungen q :: (Num a) => a -> a q x = 3*x^2 + 1 q 2 - 10 (Definition von q) ⇒ (3 * 2^2 + 1) - 10 ⇒ (3 * 4 + 1) - 10 (Definition von (^)) ⇒ (12 + 1) - 10 (Definition von (*)) ⇒ 13 - 10 (Definition von (+)) ⇒ 3 (Definition von (-)) Paßt f p1 . . . pn | g = e auf Anwendung f a1 . . . an ? Programmieren in Haskell 29 Vollständige und disjunkte Muster unique [] = [] unique [a] = [a] unique (a:(b:z)) = if a == b then unique (b:z) else a : unique (b:z) head :: [a] -> a -- vordefiniert head [] = error "head of empty list" head (a:x) = a Programmieren in Haskell 30 head’ :: [Integer] -> Maybe Integer head’ [] = Nothing head’ (a:x) = Just a head :: [a] -> a -- vordefiniert head’’’ :: [a] -> Maybe a head’’’ [] = Nothing head’’’ (a:x) = Just a Programmieren in Haskell 31 words :: String -> [String] words xs = wds [] xs ws :: String -> wds "" "" wds ws "" wds "" (’ ’:xs) wds "" ( x :xs) wds ws (’ ’:xs) wds ws ( x :xs) Programmieren in Haskell String -> [String] = [] = [reverse ws] = wds "" xs = wds [x] xs = (reverse ws) : wds "" xs = wds (x:ws) xs 32 Bindungsstärken und Assoziativitäten Bindungsstärke 9 8 7 6 5 4 4 3 2 1 0 Programmieren in Haskell linksassoziativ !! nicht assoziativ rechtsassoziativ . ^, ^^, ** \\ ==, /=, <, <=, >, >=, ‘elem‘, ‘notElem‘ :, ++ *, /, ‘div‘, ‘mod‘, ‘rem‘, ‘quot‘ +, - && || >>, >>= $, ‘seq‘ 33 Fallunterscheidungen length :: [a] -> Int length [] = 0 length (a:as) = 1 + length as -- vordefiniert length’ :: [a] -> Int length’ as = case as of [] -> 0 a:as’ -> 1 + length’ as’ last’ :: [a] -> a last’ as = case reverse as of a:as’ -> a Programmieren in Haskell 34 Allgemeine Form von Fallunterscheidungen case e of { p1 m1 ; . . . ;pn mn } Die pi sind beliebige Muster und die mi nehmen wahlweise eine der beiden folgenden Formen an: -> e where { d1 ; . . . ;dp } oder | g1 -> e1 ... | gq -> eq where { d1 ; . . . ;dp } Programmieren in Haskell 35 words :: String -> [String] -- vordefiniert words cs = case dropSpaces cs of [] -> [] cs’ -> w : words cs’’ where (w,cs’’) = splitWord cs’ minimum0’ :: (Ord a) => [a] -> Maybe minimum0’ [] = Nothing minimum0’ (a:as) = case minimum0’ as Nothing -> Just Just m -> Just Programmieren in Haskell a of a (min a m) 36 Funktionsausdrücke Ist n + 1 ein Wert oder eine Funktion? Als Funktion: f n = n + 1 \n -> n + 1 Allgemeine Form eines Funktionsausdrucks: \p1 . . . pn -> e . \p1 p2 -> e als Abkürzung für \p1 -> \p2 -> e Programmieren in Haskell 37 Gestaffelte Funktionen add :: Integer -> Integer -> Integer add m n = m + n add’ :: Integer -> (Integer -> Integer) add’ = \m -> \n -> m + n Programmieren in Haskell 38 Gestaffelte Funktionen add :: Integer -> Integer -> Integer add m n = m + n add’ :: Integer -> (Integer -> Integer) add’ = \m -> \n -> m + n add0 :: (Integer,Integer) -> Integer add0 (m,n) = m + n dup :: a -> (a,a) dup a = (a,a) double :: Integer -> Integer double n = add0 (dup n) Programmieren in Haskell 38 note :: Int -> Int -> Dauer -> Musik note oct h d = Note (12 * oct + h) d note Eine Note in einer beliebigen Oktave und von beliebiger Höhe und Dauer. note 2 Eine Note in der dritten Oktave und von beliebiger Höhe und Dauer. note 2 ef Die Note f in der dritten Oktave und von beliebiger Dauer. note 2 ef (1/4) ergibt Note 29 (1/4). Programmieren in Haskell 39 Lokale Definitionen mit let power’ :: (Integral b, Num a) => a -> b -> a power’ x n = if n == 0 then 1 else let y = power’ (x*x) (n ‘div‘ 2) in if n ‘mod‘ 2 == 0 then y else y*x Allgemein: let {e1 ; . . . ; en } in e . Programmieren in Haskell 40 Binärbäume 3 1 1 2 2 Programmieren in Haskell 3 4 41 Binärbäume 3 1 1 2 2 data Tree a = Nil | Leaf a | Br (Tree a) (Tree a) Programmieren in Haskell 3 4 deriving Show 41 3 1 t1, t2 :: Tree Integer 2 t1 = Br (Br (Leaf 1) (Leaf 2)) (Leaf 3) 1 t2 = Br (Br (Leaf 1) Nil) (Br (Br (Leaf 2) (Leaf 3)) (Br Nil (Leaf 4))) 2 Programmieren in Haskell 3 4 42 leaves leaves leaves leaves :: Tree a -> [a] Nil = [] (Leaf a) = [a] (Br l r) = leaves l ++ leaves r leftist :: [a] -> Tree a leftist [] = Nil leftist (a:as) = Br (leftist as) (Leaf a) Programmieren in Haskell 43 leaves’ leaves’ leaves’ leaves’ leaves’ :: Tree a -> [a] Nil = (Leaf a) = (Br Nil r) = (Br (Leaf a) r) = Programmieren in Haskell [] [a] leaves’ r a : leaves’ r 44 leaves’ leaves’ leaves’ leaves’ leaves’ :: Tree a -> [a] Nil = (Leaf a) = (Br Nil r) = (Br (Leaf a) r) = [] [a] leaves’ r a : leaves’ r leaves’ (Br (Br l’ r’) r) = leaves’ (Br l’ (Br r’ r)) v t Programmieren in Haskell u =⇒ t u v 44 leaves’ leaves’ leaves’ leaves’ leaves’ leaves’ :: Tree a -> [a] Nil (Leaf a) (Br Nil r) (Br (Leaf a) r) (Br (Br l’ r’) r) = = = = = [] [a] leaves’ r a : leaves’ r leaves’ (Br l’ (Br r’ r)) leaves’ (Br (Br (Leaf 1) (Leaf 2)) (Leaf 3)) ⇒ leaves’ (Br (Leaf 1) (Br (Leaf 2) (Leaf 3))) (Def. leaves’.5) ⇒ 1 : leaves’ (Br (Leaf 2) (Leaf 3)) (Def. leaves’.4) ⇒ 1 : 2 : leaves’ (Leaf 3) (Def. leaves’.4) ⇒ 1 : 2 : [3] (Def. leaves’.2) Programmieren in Haskell 45 build :: [a] -> Tree a build [] = Nil build [a] = Leaf a build as = Br (build (take k as)) (build (drop k as)) where k = length as ‘div‘ 2 take :: Int -> [a] -> [a] take n (a:as) | n > 0 = a:take (n-1) as take _ _ = [] Programmieren in Haskell 46 build [1..11]: 1 2 3 6 4 5 9 7 8 10 11 11 = 5+6 = (2 + 3) + (3 + 3) = ((1 + 1) + (1 + 2)) + ((1 + 2) + (1 + 2)) = ((1 + 1) + (1 + (1 + 1))) + ((1 + (1 + 1)) + (1 + (1 + 1))) . Programmieren in Haskell 47 build’ :: [a] -> Tree a build’ as = fst (buildSplit (length as) as) buildSplit :: Int -> [a] -> (Tree a, [a]) buildSplit 0 as = (Nil, as) buildSplit 1 as = (Leaf (head as), tail as) buildSplit n as = (Br l r, as’’) where k = n ‘div‘ 2 (l,as’) = buildSplit k as (r,as’’) = buildSplit (n-k) as’ Programmieren in Haskell 48 Syntaktischer Zucker / Kernsprache • Mustergleichungen / case-Ausdrücke length [] = 0 length (x:xs) = 1 + length xs length’ xs = case xs of [] -> 0 (x:xs) -> 1 + length’ xs • Funktionsdefinitionen / Funktions-Ausdrücke add m n = m + n add’ = \m -> \n -> m + n Programmieren in Haskell 49 • where-Klauseln / let-Ausdrücke double n = add0 (dup n) where dup a = (a,a) double n = let dup a = (a,a) in add0 (dup n) Programmieren in Haskell 50 Auswertung von Fallunterscheidungen case C e1 ...ek of {...; C x1 ...xk -> e;...} (case-Regel) ⇒ e[x1 /e1 , . . . , xn /en ] Programmieren in Haskell 51 abs :: (Ord a, Num a) => a -> a abs n = case n >= 0 of True -> n False -> -n encode :: (Num a) => Maybe a -> a encode x = case x of Nothing -> -1 Just n -> abs n encode (Just 5) ⇒ case Just 5 of {Nothing -> -1; Just n -> abs n} (Def. encode) ⇒ abs 5 (case − Regel) ⇒ case 5 >= 0 of {True -> 5; False -> -5} (Def. abs) ⇒ case True of {True -> 5; False -> -5} (Def. >=) ⇒ 5 Programmieren in Haskell (case − Regel) 52 Auswertung von Funktionsanwendungen (\x -> e) a ⇒ e[x/a] Programmieren in Haskell (β-Regel) (1) 53 abs = encode = \n -> case n >= 0 of {True -> n; False -> -n} \x -> case x of {Nothing -> -1; Just n -> abs n} encode (Just 5) (Def. encode) ⇒ (\x-> case x of {Nothing-> -1; Just n-> abs n}) (Just 5) ⇒ case Just 5 of {Nothing -> -1; Just n -> abs n} ⇒ abs 5 ⇒ (\n -> case n >= 0 of {True -> n; False -> -n}) 5 (Def. abs) ⇒ case 5 >= 0 of {True -> 5; False -> -5} (β − Regel) ⇒ case True of {True -> 5; False -> -5} (Def. (>=)) ⇒ 5 Programmieren in Haskell (β − Regel) (case − Regel) (case − Regel) 54 Auswertung von lokalen Definitionen let {x1 = e1 ;...;xn = en } in e ⇒ e[x1 /let {x1 = e1 ;...;xn = en } in e1 , . . . , (let-Regel) xn /let {x1 = e1 ;...;xn = en } in en ] Programmieren in Haskell 55 rep n a = let x = a:x in take n x rep 2 8 Programmieren in Haskell ⇒ let x = 8:x in take 2 x ⇒ take 2 (let x = 8:x in 8:x) (let − Regel) ⇒ take 2 (8:let x = 8:x in 8:x) (let − Regel) ⇒ 8:take 1 (let x = 8:x in 8:x) (Def. take) ⇒ 8:take 1 (8:let x = 8:x in 8:x) (let − Regel) ⇒ 8:8:take 0 (let x = 8:x in 8:x) (Def. take) ⇒ 8:8:[] (Def. take) (Def. rep) 56