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