Haskell
Falco Nogatz
24. Juli 2012
PDP-Repetitorium
... stehen auch mit Wertebereich auf dem Cheatsheet!
+, -, *, /, div, mod
^ (ganzzahliger Exponent), **
==, /=, <, >, <=, >=
&&, ||, not
Jede zweistellige Funktion kann sowohl in Infix- als auch mittels Klammerung in Präfix-Notation aufgerufen werden.
5 + 3 (+) 5 3 5 / 3 (/) 5 3 (&&) True False True && False mod 5 3 5 `mod` 3 div 5 3 5 `div` 3
Einfache Typangaben von Funktionen werden in folgender Form gemacht:
Typ -> Typ -> ... -> Typ
Beispiele:
(+) :: Double -> Double -> Double (/) :: Double -> Double -> Double (&&) :: Bool -> Bool -> Bool (mod) :: Int -> Int -> Int (==) :: Char -> Char -> Bool
Offensichtlich gibt es Funktionen, die für mehrere konkrete Typen anwendbar sind, z.B.:
(==)(für alle Werte, die sich vergleichen lassen)
(+)(für jegliche Zahlen)
(mod)(für ganze Zahlen)
Die o.g. Funktionen sind also für mehr als die dargestellten Typen definiert:
(+) :: Double -> Double -> Double (+) :: Int -> Int -> Int (+) :: Float -> Float -> Float (+) :: Integer -> Integer -> Integer
Unter Nutzung von Typvariablen a, b, ... kann deutlich gemacht werden, dass mehrere Typen (ggf. einer angegebenen Typklasse, siehe nächste Folie) eingesetzt werden können.
(+) :: a -> a -> a
[a] -> Intheißt allgemeinster Typ für
length.
Manche Funktionen sind zwar polymorph, aber dennoch nur für bestimmte Typen anwendbar. So lässt sich (+) eigentlich nur auf Zahlen anwenden. Es gibt daher eine Reihe von Typklassen:
Die Einschränkung von Typvariablen durch Typklassen wird wie folgt dargestellt:
(Typklasse a, Typklasse b, ...) => Typ -> Typ -> ... -> Typ
Für die vorangegangenen Beispiele gilt also:
(==) :: (Eq a) => a -> a -> Bool
(+) :: (Num a) => a -> a -> a
(mod) :: (Integral a) => a -> a -> a
(&&) :: Bool -> Bool -> Bool allgemeiner geht nicht
Was bedeutet folgender Funktionstyp?
(>=) :: (Ord a) => a -> a -> Bool
a -> (a -> a)
In Haskell implizite Klammerung nach rechts, d.h. zweiter Fall.
Jede Funktion in Haskell kann partiell aufgerufen werden. Achtung bei zweistelligen Operatoren!
Beispiele:
((+) 1) Nachfolger ((*) 10) Zehnfache einer Zahl ((^) 3) 3 hoch Zahl ((>=) 0) Zahl kleiner 0?
Achtung: Haskell "verdreht" zweistellige transitive Funktionen zur natürlichsprachigen Bedeutung:
(^ 3) Zahl hoch 3 (>= 0) Zahl größer gleich 0?
(T1, T2, ..., Tn)bezeichnet n-Tupel, dessen i-te Komponente vom Typ Ti ist
()
Beispiele:
(True, False) :: (Bool, Bool)
(1, 'c', "test") :: (Num a) => (a, Char, String)
((True, False), (False, False)) :: ((Bool, Bool), (Bool, Bool))
(1) :: (Num a) => a Zahl in Klammern, kein 1-Tupel!
[], oder sie besteht aus einem Element und einer Restliste
(x:xs)
[1,2,3,4]
Beispiele
[True, False, False] :: [Bool] [1,2,3,4] :: (Num a) => [a] [1,2,3,4.2] :: (Fractional a) => [a] [(True,0), (False,1), (True,2)] :: (Num a) => [(Bool, a)] [] :: [a]
Komfortable Möglichkeit, Listen als arithmetische Folge zu definieren:
[n..m]→
[n,n+1,...,m-1,m]
[n,k..m]→
[n,n+(k-n),n+2*(k-n)...,m]
Beispiele
[1..10] → [1,2,3,4,5,6,7,8,9,10]
[1,3..10] → [1,3,5,7,9]
[10,7..(-3)] → [10,7,4,1,-2]
['a'..'f'] → ['a','b','c','d','e','f']
[1,3..] → [1,3,5,7,...] unendliche Liste
[Resultatausdruck | Generator, Wächter]
[n^2 | n <- [1..], even n]
-- Alle Teiler einer Zahl divisors :: (Integral a) => a -> [a] divisors n = [t | t <‐ [1..n], n `mod` t == 0] -- Ist Zahl Primzahl? isPrime :: (Integral a) => a -> Bool isPrime n = divisors n == [1,n] -- Liste aller Primzahlen bis einer Zahl primes :: (Integral a) => a -> [a] primes n = [p | p <‐ [2..n], isPrime p]
Eine Listenkomprehension kann mehrere Generatoren besitzen:
[(x,y) | x <- [1..3], y <- [True, False]] → [(1,True),(1,False),(2,True),(2,False),(3,True),(3,False)] [(x,y) | x <- [1..4], y <- [True, False], y, x `mod` 2 == 0] → [(2,True),(4,True)]
Je weiter rechts ein Generator steht, desto schneller verändert er sich, analog zu verschachtelten Schleifen.
... die auch auf dem Cheatsheet stehen:
head :: [a] -> a head [1..10] == 1
tail :: [a] -> [a] tail [1..10] == [2..10]
last :: [a] -> a last [1..10] == 10
take :: Int -> [a] -> [a] take 5 [1..10] == [1..5]
drop :: Int -> [a] -> [a] drop 5 [1..10] == [6..10]
elem :: Eq a => a -> [a] -> Bool elem 5 [1..10] == True
length :: [a] -> Int length [1..10] == 10
(++) :: [a] -> [a] -> [a] [1..10] ++ [11..20] == [1..20]
reverse :: [a] -> [a] reverse [1..10] == [10,9..1]
concat :: [[a]] -> [a] concat [[1..10],[11..20],[21..30]] == [1..30]
product)
sum :: Num a => [a] -> a sum [1..10] == 55
filter :: (a -> Bool) -> [a] -> [a] filter even [1..10] == [2,4..10]
Einfache Funktionen können einfach aufgeschrieben werden:
muenzen :: [Int] muenzen = [1,2,5,10,20,50,100,200] average :: (Fractional a) => a -> a -> a -> a average a b c = (a+b+c)/3 inc :: (Integral a) => a -> a inc = (+1) -- Partieller Aufruf
not b = if b == True then False else True
not b | b == True = False | otherwise = True
not True = False not False = True
not b = case b of True -> False False -> True
Form:
if Bedingung::Bool then Ausdruck1::a else Ausdruck2::a
Kein Teil kann weggelassen werden: Da es ein Ausdruck und keine Anweisung ist, gibt es kein if ohne else - irgendeinen Rückgabewert muss es geben!
Beispiele:
abs n = if n >= 0 then n else (-1)*n fak n = if n == 0 then 1 else n*(fak (n-1))
Statt geschachtelter bedingter Ausdrücke können Guards verwendet werden:
-- verschachtelt: sign n = if n < 0 then -1 else if n == 0 then 0 else 1 -- mit Guards: sign' n | n < 0 = -1 | n == 0 = 0 | otherweise = 1
otherwiseals letztes verwenden!
Vergleich auf Basis von Mustern, d.h. ohne Nutzung von Bedingungen.
Beispiele:
or2 :: Bool -> Bool -> Bool oder True _ = True -- Wildcard-Operator oder False a = a fak 0 = 1 fak n = n * fak (n-1)
Als Muster können auch Variablen oder komplexe Muster dienen. Typisch ist dies etwa bei Listen.
length :: [a] -> Int
length [] = 0
length (x:xs) = 1 + length xs
Sollen mehrere Muster in einem Ausdruck dargestellt werden, bietet sich ein Case-Ausdruck an:
length l = case l of [] -> 0 (x:xs) -> 1 + length xs neg b = case b of True -> False False -> True
Häufig braucht man Hilfsfunktionen nur in einem konkreten Funktionsaufruf und möchte sie daher nicht global definieren. Stattdessen können sie per
letund
wherelokal definiert werden.
let Bezeichner = Ausdruck1 in Ausdruck2 let m = [1,2,5,10,20,50,100,200] in [x+y+z | x <- m, y <- m, z <- m]
Ausdruck1 = Ausdruck2 where Bezeichner = Ausdruck3 werte = [x+y+z | x <- m, y <- m, z <- m] where m = [1,2,5,10,20,50,100,200]
Ähnlich zu lokalen Funktionen: Angabe einer Funktion ohne Namen.
-- über lokale Definition let myf i = i `mod` 3 == 1 && i `mod` 7 == 2 in filter myf [1..] -- [16,37,58,79,100,121,142,163,184,...] -- über Lambda-Ausdruck filter (\i -> i `mod` 3 == 1 && i `mod` 7 == 2) [1..] -- Resultat? (\x y -> x*y) 2 3 -- 6 (\x y -> x*y) 2 -- (\y -> 2*y)
sum [] = 0 sum (x:xs) = x + sum xs product [] = 1 product (x:xs) = x * product xs and [] = True and (x:xs) = x && and xs
Verallgemeinerung:
foldr f i [] = i foldr f i (x:xs) = f x (foldr f i xs)
Es gibt foldr für rechtsassoziative und foldl für linksassoziative Funktionen:
foldr f i [] = i foldr f i (x:xs) = f x (foldr f i xs) foldl f i [] = i foldl f i (x:xs) = foldl f (f i x) xs
Fazit: Nur äquivalent für assoziative Funktionen!
Durch Typsynonyme können weitere Namen für bereits existierende Typen gesetzt werden, etwa String für [Char].
type String = [Char] type IntPaar = (Int, Int)
... für die Klausur wenig relevant.
... können über das Schlüsselwort
datadefiniert werden.
data Programmiersprache = Haskell | Prolog | CHR | Java deriving (Eq, Ord, Enum, Show)
data Shape = Circle Float | Rectangle Float Float | Square Float
Typdefinitionen (Programmiersprache) und Konstruktoren (Haskell) werden groß geschrieben.
Den Konstruktoren können zusätzliche Informationen als Parameter übergeben werden:
data Shape = Circle Float | Rectangle Float Float | Square Float
Diese Konstruktoren können als Muster verwendet werden:
area (Circle r) = 3.14159 * r^2 area (Rectangle a b) = a * b area (Square a) = a^2
Rekursive Datentypen stellen Bäume dar. Ein einfacher Binärbaum wäre:
data BinTree = Empty | Node BinTree BinTree -- Mögliche Instanz: Node (Node Empty (Node Empty Empty)) Empty
Der eben gezeigte Baum ist wenig sinnvoll, da er keine Werte halten kann. Eine Alternative wäre:
data BinIntTree = Empty | Node Int BinIntTree BinIntTree -- Typvariable a data BinTree a = Empty | Node a (BinTree a) (BinTree a) -- Mögliche Instanzen: type BinCharTree = BinTree Char Node 'c' (Node 'd' Empty (Node 'e' Empty Empty)) Empty
Gegeben sei folgender Datentyp aus Aufgabe 8:
type Plaetze = Int data Stecker = Steckdosenleiste Plaetze [Stecker] | Geraet String
Frage: Wie sieht eine fold-Funktion, analog zu foldr und foldl, für diesen Datentypen aus?
Allgemeines Vorgehen:
foldStecker :: (String -> a) -> (Plaetze -> [a] -> a) -> Stecker -> a foldStecker gf sf = m where m (Geraet string) = gf string m (Steckdosenleiste p s) = sf p (map m s)
... gibt's mit Lösungsvorschlägen hier:
fnogatz.github.io/talks/pdp-rep-12/haskell/exercises.html
Thanks to Benjamin Erb for the html5slides and uulm template.
Title image: Miran Lipovača under CC BY-NC-SA 3.0