Falco Nogatz
Master-Student @ uulm
Master-Student @ uulm
Bool
(auch: Boolean
): True
oder False
Char
: 'a'
, '2'
, '\n'
String
(ist: [Char]
): "Name"
, ['N','a','m','e']
Int
: best. WertebereichInteger
: beliebige GrößeFloat
Double
... 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:
Eq
: alle primitiven DatentypenOrd
: Bool, Char, Int, Integer, Float, Double (impliziert Eq
)Num
: Int, Integer, Float, DoubleFractional
: Float, Double (impliziert Num
)Integral
: Int, Integer (impliziert Num
)Enum
: Char, Int, Integer, Float, DoubleDie 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 gleich 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 zu 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]
map :: (a -> b) -> [a] -> [b] map (*2) [1..10] == [2,4..20]
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 | otherwise = 1
otherwiseals letztes verwenden!
Vergleich auf Basis von Mustern, d.h. ohne Nutzung von Bedingungen.
Beispiele:
oder :: 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
let
und where
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)
foldl
, foldr
und andere fold
'ssum [] = 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
foldTree
usw.Gegeben sei folgender Datentyp aus Aufgabe 8:
type Quantity = Integer data HuffmanTree = Node HuffmanTree HuffmanTree | Char Quantity Char deriving (Eq, Ord, Show)
Frage: Wie sieht eine fold-Funktion, analog zu foldr und foldl, für diesen Datentypen aus?
Allgemeines Vorgehen:
foldHuffmanTree :: (Quantity -> Char -> a) -> (a -> a -> a) -> HuffmanTree -> a foldHuffmanTree cf nf = m where m (Char q c) = cf q c m (Node h1 h2) = nf (m h1) (m h2)
fold
auf rekursiven Datentypen... gibt's mit Lösungsvorschlägen hier:
fnogatz.github.io/talks/pdp-rep-14/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