Christoph - Funktionale Programmierung mit Haskell

Werbung
Functional Programming
Christoph Dittmann
7. Juli 2007
Haskell
●
Entstanden 1987
●
Benannt nach Mathematiker Haskell B. Curry
●
„Haskell 98 revised“ (2003)
●
Plattformunabhängig
Haskell
●
Rein funktional (keine Seiteneffekte)
●
Stark und statisch typisiert
●
Non-strict (lazy evaluation)
Syntax
●
Konstanten
–
●
v = ...
Funktionen
–
f p1 ... pn = ...
Definition und Aufruf
●
Definition
–
●
square x = x * x
Aufruf
–
square 4
●
16
Signatur
●
●
Zwei Doppelpunkte, danach Typ
f :: Char -> Int
–
●
int f(char);
h :: Double -> Int -> Int
–
int h(double, int);
●
Signatur und Definition getrennt
●
Automatisch abgeleitete Signaturen
Signatur
●
Vordefinierte Typen (Auswahl)
–
–
–
–
Bool, Int, Char, Float, Double
Integer, String
[a]
a -> b
Case Sensitivity
●
Großer Anfangsbuchstabe: Definierter Datentyp
●
Kleiner Anfangsbuchstabe
–
Normal: Bezeichner
–
In Signatur: Beliebiger Datentyp
Case Sensitivity
●
Großer Anfangsbuchstabe: Definierter Datentyp
●
Kleiner Anfangsbuchstabe
●
–
Normal: Bezeichner
–
In Signatur: Beliebiger Datentyp
replicate :: Int -> a -> [a]
–
replicate 3 1
●
–
[1, 1, 1]
replicate 4 'x'
●
['x', 'x', 'x', 'x']
Funktionen als Werte
●
Anwendung auf jedes Element
–
●
map :: (a -> b) -> [a] -> [b]
Aufruf
–
map square [1, 2, 3]
●
[1, 4, 9]
Funktionen als Werte
●
Anwendung auf jedes Element
–
●
map :: (a -> b) -> [a] -> [b]
Aufruf
–
map square [1, 2, 3]
●
●
[1, 4, 9]
Partielle Funktionsanwendung
–
map (1+) [1, 2, 3]
●
[2, 3, 4]
Pattern Matching
●
●
●
Definitionen werden in Reihenfolge probiert
Kleiner Anfangsbuchstabe passt auf alles
isZero :: Int -> Bool
isZero 0 = True
isZero x = False
Pattern Matching
●
●
●
Definitionen werden in Reihenfolge probiert
Kleiner Anfangsbuchstabe passt auf alles
replicate :: Int -> a -> [a]
replicate 0 x = []
replicate n x =
x : replicate (n-1) x
Pattern Matching
●
●
●
●
Definitionen werden in Reihenfolge probiert
Kleiner Anfangsbuchstabe passt auf alles
replicate :: Int -> a -> [a]
replicate 0 x = []
replicate n x =
x : replicate (n-1) x
Listenkonstruktor (cons)
–
–
(:) :: a -> [a] -> [a]
[1,2,3] == 1 : 2 : 3 : [] == [1..3]
Pattern Matching
●
●
●
Definitionen werden in Reihenfolge probiert
Kleiner Anfangsbuchstabe passt auf alles
replicate :: Int -> a -> [a]
replicate n x = map (const x) [1..n]
Pattern Matching
●
●
●
Definitionen werden in Reihenfolge probiert
Kleiner Anfangsbuchstabe passt auf alles
replicate :: Int -> a -> [a]
replicate n x = map (const x) [1..n]
Lazy Evaluation
●
●
„call-by-need“
square (1 + 2)
–
–
–
=> (1 + 2) * (1 + 2)
=> 3 * 3
=> 9
Lazy Evaluation
●
Listenkopf
–
●
head [1, 2, 3] = 1
head (replicate 1000000000 'a')
Lazy Evaluation
●
Listenkopf
–
●
head (replicate 1000000000 'a')
–
–
●
=> head ('a':replicate 999999999 'a')
=> 'a'
Ähnlich Unix-Pipes
–
●
head [1, 2, 3] = 1
Keine großen temporären Objekte
„Glue“
Algebraischer Datentyp (ADT)
●
Konstruktor verpackt Daten
●
Konstruktor wird nicht ausgeführt
●
Pattern Matching zum Verarbeiten notwendig
●
Mächtiges Typsystem
Enum als ADT
●
data Bool = False | True
●
Zwei Konstruktoren ohne Parameter
●
Konvertierung nach Int:
–
toInt :: Bool -> Int
toInt False = 0
toInt True = 1
„Zeiger“ als ADT
●
data Maybe a = Nothing | Just a
●
Entweder nichts oder einfach ein Wert
●
Beispiel:
–
elemIndex :: a -> [a] -> Maybe Int
„Zeiger“ als ADT
●
data Maybe a = Nothing | Just a
●
Entweder nichts oder einfach ein Wert
●
Beispiel:
–
●
elemIndex :: a -> [a] -> Maybe Int
Aufruf
–
elemIndex "a" ["b", "a", "c"]
●
–
Just 1
elemIndex "x" ["b", "a", "c"]
●
Nothing
Klassen (Interfaces)
●
Nur ein Interface, keine Klasse im C++-Sinne
●
Beispiel: Test auf Gleichheit
–
class Eq a where
(==) :: a -> a -> Bool
Klassen (Interfaces)
●
Nur ein Interface, keine Klasse im C++-Sinne
●
Beispiel: Test auf Gleichheit
–
●
Instanz für Bool
–
●
class Eq a where
(==) :: a -> a -> Bool
instance Eq Bool
False == False
True == True
x
== y
where
= True
= True
= False
Instanzen können nachträglich erstellt werden
Polymorphie mit Klassen
●
Eingeschränkte Polymorphie
–
–
elemIndex :: Eq a => a -> [a]
-> Maybe Int
square :: Num a => a -> a
square x = x * x
Polymorphie mit Klassen
●
Eingeschränkte Polymorphie
–
–
●
(Beinahes) C++-Äquivalent
–
●
elemIndex :: Eq a => a -> [a]
-> Maybe Int
square :: Num a => a -> a
square x = x * x
template<typename T>
T square(const T& x) {
return x * x; }
Aber: Signatur spiegelt operator* nicht wider
Seiteneffekte
Seiteneffekte
●
Keine globalen Variablen
●
Funktionen ohne Parameter sind Konstanten
http://www.xkcd.com/c221.html
Seiteneffekte
●
Nicht möglich
–
getRandomNumber :: Int
–
●
Manuelle Lösung
–
getRandomNumber :: GenState
-> (GenState, Int)
Seiteneffekte versteckt
●
Kapselung des Status
–
–
●
getRandomNumber :: State GenState Int
data State s a = State (s -> (s, a))
Manuelle Lösung
–
getRandomNumber :: GenState
-> (GenState, Int)
Input / Output
●
Standard-Ein-/Ausgabe
–
–
●
getLine
::
->
putStrLn ::
->
->
InputBuffer
(InputBuffer, String)
String
OutputBuffer
(OutputBuffer, ())
Gemeinsames Muster: State
–
–
data State s a = State (s -> (s, a))
Input / Output
●
●
Standard-Ein-/Ausgabe
–
getLine
:: State InputBuffer String
–
putStrLn :: String
-> State OutputBuffer ()
Gemeinsames Muster: State
–
–
data State s a = State (s -> (s, a))
Input / Output
●
●
Standard-Ein-/Ausgabe
–
getLine
:: State World String
–
putStrLn :: String
-> State World ()
Gemeinsames Muster: State
–
–
data State s a = State (s -> (s, a))
Input / Output
●
●
●
Standard-Ein-/Ausgabe
–
getLine
:: IO String
–
putStrLn :: String
-> IO ()
Gemeinsames Muster: State
IO t kann I/O-Aktionen ausführen, bevor ein t
geliefert wird
–
Input / Output
●
Standard-Ein-/Ausgabe
–
–
●
●
getLine :: IO String
getLine :: World -> (World, String)
putStrLn :: String
-> IO ()
Gemeinsames Muster: State
IO t kann I/O-Aktionen ausführen, bevor ein t
geliefert wird
–
Input / Output
●
Verkettung
–
●
:: IO a -> (a -> IO b) -> IO b
Beispiel
–
●
bind
getLine `bind` putStrLn
mit
–
–
getLine :: IO String
putStrLn :: String -> IO ()
Input / Output
●
Verkettung
–
–
bind
:: IO a -> (a -> IO b) -> IO b
return :: a -> IO a
Input / Output
●
Verkettung
–
–
●
bind
:: IO a -> (a -> IO b) -> IO b
return :: a -> IO a
Beispiel
–
–
getLine `bind` (\a ->
return (reverse a))
Control.Parallel
●
Klassisches map
–
●
map
:: (a -> b) -> [a] -> [b]
Modul Control.Parallel bietet
–
parMap :: (a -> b) -> [a] -> [b]
Control.Parallel
●
Klassisches map
–
●
parMap :: (a -> b) -> [a] -> [b]
Nur möglich durch Fehlen von Seiteneffekten
–
–
●
:: (a -> b) -> [a] -> [b]
Modul Control.Parallel bietet
–
●
map
parMap print ["Hello", " world!"]
:: [IO ()]
Keine Ausgabe durch parMap
IO
●
Strikte Markierung von Funktionen mit
Seiteneffekten durch IO (tainting)
●
Aus IO ist reiner Code ausführbar
●
Aus reinem Code kein IO
●
Referentielle Transparenz:
–
–
y
= f x
foo = y * y
foo = f x * f x
QuickCheck
●
Unit-Tests
●
Generierung von zufälligen Eingabedaten
●
Erwartet Funktion des Typs
–
●
f :: <anything> -> Bool
Testet, ob f immer True liefert
QuickCheck
●
Beispiel
–
–
f :: [a] -> Bool
f xs = length (sort xs) == length xs
quickCheck f
●
OK, passed 100 tests.
QuickCheck
●
Beispiel
–
–
r :: Eq b => (a -> b) -> a -> Bool
r g x = g x == g x
quickCheck r
●
OK, passed 100 tests.
QuickCheck
●
Beispiel
–
–
r :: Eq b => (a -> b) -> a -> Bool
r g x = g x == g x
quickCheck r
●
●
OK, passed 100 tests.
Typ von quickCheck
–
quickCheck :: Testable a => a -> IO ()
Pro & Contra
Contra
●
Steile Lernkurve
●
Völlig neuer Programmier-Stil
●
Niedriger Bekanntheitsgrad
●
Als „akademische Sprache“ abgetan
●
Vorhersage des Speicherbedarfs schwierig
Pro
●
Sehr hohe Abstraktion
●
Sehr sicheres Typsystem
●
Referentielle Transparenz liefert Garantien
–
Unit Tests leichter möglich (QuickCheck)
–
Gute Optimierungsmöglichkeiten
Pro
Duncan Coutts, Don Stewart and Roman Leshchinskiy:
Rewriting Haskell Strings, 2007
Pro
●
Sehr hohe Abstraktion
●
Sehr sicheres Typsystem
●
Referentielle Transparenz liefert Garantien
–
Unit Tests leichter möglich (QuickCheck)
–
Gute Optimierungsmöglichkeiten
Pro
●
Sehr hohe Abstraktion
●
Sehr sicheres Typsystem
●
Referentielle Transparenz liefert Garantien
–
Unit Tests leichter möglich (QuickCheck)
–
Gute Optimierungsmöglichkeiten
●
Kompakter Code (QuickCheck, Xmonad)
●
Gewappnet für parallele Architekturen
Herunterladen