Universität Bielefeld Programmieren in Haskell Giegerich Programmieren in Haskell WS 2013/2014 LambdaExpressions Gestaffelte Funktionen Higher Order Functions Robert Giegerich Universität Bielefeld AG Praktische Informatik 11. Dezember 2013 Funktionen höherer Ordnung Universität Bielefeld Programmieren in Haskell Giegerich Worum geht es heute? LambdaExpressions In Haskell gibt es Funktionen, die Funktionen als Argument haben oder als Ergebnis liefern. Diese nennt man Funktionen höherer Ordnung. Davon kennen wir bisher Beispiele wie map oder (.) Was man insgesamt damit machen kann, ist damit noch lange nicht erschöpft ... Gestaffelte Funktionen Higher Order Functions “Funktionen als Bürger erster Klasse” Universität Bielefeld Programmieren in Haskell Giegerich LambdaExpressions Funktionen definieren kann man in jeder Programmiersprache. Eine funktionale Programmiersprache erlaubt auch Funktionen in Datenstrukturen, z.B. [(+), (-), (*)] Funktionen als Argmente, z.B. map square [1..100] Funktionen als Ergebnisse, z.B. f . g Gestaffelte Funktionen Higher Order Functions Wie entstehen Funktionen? Benannte Funktionen werden vom Programmierer definiert. Unbenannte Funktionen (engl. anonymous functions) entstehen Universität Bielefeld Programmieren in Haskell Giegerich LambdaExpressions durch teilweise Anwendung benannter Funktionen, z.B. (3+), (‘div‘ 2), ("niemals"++) map reverse Note ce (‘Note‘ (1/4)) map (1:) all_equal 3 4 Natürlich kann man diesen dann auch Namen geben: startnie = ("niemals"++) durch Verknüpfung von Funktionen, z.B. reverse . concat oder durch ... Gestaffelte Funktionen Higher Order Functions Lambda-Abstraktion Universität Bielefeld Programmieren in Haskell Abstraktion verwandelt einen Ausdruck in eine Funktion 3 ∗ a − b2 + 5 λa, b → 3 ∗ a − b 2 + 5 Giegerich ist ein Ausdruck LambdaExpressions ist eine Funktion von a und b Gestaffelte Funktionen (λa, b → 3 ∗ a − b 2 + 5)(2, 3) ist eine Funktionsanwendung und berechnet den Wert 2 Higher Order Functions Abstraktion: Vom konkreten zum Allgemeinen Dur3Klang cDur3kl = Note ce (1/4) :+: Note eh (1/4) :+: Note ge (1/4) versus dur3kl = \(t,d) -> Note t d :+: Note (t+4) d :+: Note (t+7) d Auf der rechten Seite der Gleichung steht eine anonyme Funktion (die links einen Namen bekommt) Lambda-Expressions in Haskell Universität Bielefeld Programmieren in Haskell Man nennt sie ... Lambda-Abstractions Lambda-Ausdrücke Anonyme Funktionen Example 1 2 3 4 (\ a b c - > b ^2 -4* a * c ) 1 5 4 map (\ x -> x ^2 + 1) [1..10] filter (\ x -> x ‘mod ‘ 5 == 0) [1..100] map (\( x , y ) -> x + y ) ( zip [1..10] [1..10]) Siehe online Beispiele ... Giegerich LambdaExpressions Gestaffelte Funktionen Higher Order Functions Syntax Universität Bielefeld Programmieren in Haskell Lambda-Ausdruck Einfache und allgemeine Form: Giegerich λx1 . . . xn → expr (1) LambdaExpressions λpat1 . . . patn → expr (2) Gestaffelte Funktionen Higher Order Functions 1 2 Bzw: in Haskell \ x1 ... xn -> expr \ pat1 ... patn -> expr definiert eine anonyme, n-stellige Funktion. Wichtig: Lambda-Ausdruck ist ein Ausdruck, sein Wert eine Funktion. Er kann überall stehen, wo Ausdrücke erlaubt sind, z.B. als Argument einer Funktion/ eines Lambda-Ausdrucks Wiederholung: Pattern/Muster Universität Bielefeld Programmieren in Haskell Giegerich Pattern sind Ausdrücke bestehend aus LambdaExpressions natürlichen Zahlen Gestaffelte Funktionen Werten Higher Order Functions Variablen Datentyp-Konstruktoren Wildcard-Symbol: _ aber ohne Funktionen!! (– mit Ausnahme des Musters n+1) Lambda-Patterns Universität Bielefeld Programmieren in Haskell Giegerich Lambda-Patterns erlauben pattern matching auf der “linken” Seite auch in anonymen Funktionen. Muster von mehrstelligen Konstruktoren müssen geklammert werden: Example 1 (\( x : xs ) -> x ) [4 ,3 ,2] LambdaExpressions Gestaffelte Funktionen Higher Order Functions Currying: 1-stellige versus n-stellige Funktionen Universität Bielefeld n-stellige Funktionen lassen sich stets als 1-stellige Funktionen auffassen. Man nennt sie dann “curried” oder auf Deutsch Programmieren in Haskell Giegerich LambdaExpressions curryfiziert (nach Haskell B. Curry) geschönfinkelt (nach Moses Schönfinkel) schön gefinkelt ??? gestaffelt Gestaffelte Funktionen Higher Order Functions Gestaffelte Sichtweise λpat1 pat2 . . . patn → expr (3) λpat1 → λpat2 → . . . λpatn → expr (4) (λpat1 → (λpat2 → . . . (λpatn → expr))) (5) Bzw: Beispiel: sechsmal die gleiche Funktion Example 1 2 > add : : I n t −> I n t −> I n t > add x y = x + y 3 4 5 > add ’ : : I n t −> ( I n t −> I n t ) > add ’ x y = x + y 6 7 8 > add ’ ’ : : I n t −> ( I n t −> I n t ) > add ’ ’ x = \ y −> x + y 9 10 11 > add ’ ’ ’ : : I n t −> ( I n t −> I n t ) > add ’ ’ ’ = \ x y −> x + y 12 13 14 > add ’ ’ ’ ’ : : I n t −> ( I n t −> I n t ) > add ’ ’ ’ ’ = \ x −> \ y −> x + y 15 16 17 > add ’ ’ ’ ’ ’ : : I n t −> I n t −> I n t > add ’ ’ ’ ’ ’ = \ x −> \ y −> x + y Universität Bielefeld Programmieren in Haskell Giegerich LambdaExpressions Gestaffelte Funktionen Higher Order Functions Beispiele Universität Bielefeld Programmieren in Haskell Giegerich LambdaExpressions Gestaffelte Funktionen Typen: Was meint Hugs? Higher Order Functions Currying Universität Bielefeld Programmieren in Haskell Giegerich LambdaExpressions die verschiedenen Versionen von add haben den gleichen Typ → Denn -> (function type constructor) ist rechtsassoziativ in Haskell Sichtweise: Das “zweistellige” add ist eine einstellige Funktion, die eine einstellige Funktion als Ergebnis hat Gestaffelte Funktionen Higher Order Functions Partielle Anwendung ohne Lambda Universität Bielefeld Programmieren in Haskell Giegerich LambdaExpressions Example 1 2 (1+) map (1+) [2 ,3 ,4] Gestaffelte Funktionen Higher Order Functions Kein Curry? Universität Bielefeld Programmieren in Haskell Giegerich Example 1 f (a , b ) = a + b LambdaExpressions Gestaffelte Funktionen Ausweg: 1 2 curry curry f x y Higher Order Functions :: (( a , b ) -> c ) -> a -> b -> c = f (x , y ) Zuviel Curry? 1 2 uncurry uncurry f p :: ( a -> b -> c ) -> (( a , b ) -> c ) = f ( fst p ) ( snd p ) Fazit: Currying Universität Bielefeld Programmieren in Haskell Giegerich In Haskell: Nomen est omen Funktionen sind “per-default” gestaffelt zurückgibt Staffelung vermeidbar via Tupel Funktions-Typkonstruktor (->) ist rechtsassoziativ Functions-Anwendung ist linksassoziativ: f a b ist das gleiche wie (f a) b So gesehen ist jede mehrstellige Funktion in Haskell eine Higher-Order-Function. LambdaExpressions Gestaffelte Funktionen Higher Order Functions Higher Order Functions Universität Bielefeld Programmieren in Haskell Funktionen höherer Ordnung Funktionen mit Funktionen als Parameter Funktionen mit Funktionen als Rückgabewert Giegerich LambdaExpressions Gestaffelte Funktionen Higher Order Functions In Haskell: Funktionen als “1st class citizens” sind oft von höherer Ordnung Funktionen höherer Ordnung sind “Kontrollstrukturen”, die man (im Unterschied zu imperativen Sprachen) selbst definieren kann Wiederholung Universität Bielefeld Programmieren in Haskell Giegerich 1 2 3 Bekannte map :: map map Beispiele für Funktionen höherer Ordnung: (a - > b ) -> [ a ] -> [ b ] f [] = [] f ( x : xs ) = f x : map f xs 4 5 6 filter :: ( a -> Bool ) -> [ a ] -> [ a ] filter p xs = [ x | x <- xs , p x ] LambdaExpressions Gestaffelte Funktionen Higher Order Functions Wiederholung A&D: Strukturelle Rekursion Universität Bielefeld Programmieren in Haskell Giegerich Srukturelle Rekursion auf Listen – Schema: f LambdaExpressions :: [σ] -> τ f [] = e1 f (a : as) = e2 where s = f as wobei e1 und e2 Ausdrücke vom Typ τ sind und e2 die Variablen a, as und s (nicht aber f ) enthalten darf. Gestaffelte Funktionen Higher Order Functions Beispiele Universität Bielefeld Programmieren in Haskell length Giegerich sum LambdaExpressions prod Gestaffelte Funktionen and 1 2 Alle folgen dem gleichen Schema wie z.B. and [] = True and ( x : xs ) x && ( and xs ) Funktion Terminierungs- Anwendung wert (e1) auf x (e2) length 0 1 sum 0 x prod 1 x and True x Higher Order Functions Akkumulation des Ergebnisses (e2) + + * && Schema für diese Art der strukturellen Rekursion in Haskell Universität Bielefeld Programmieren in Haskell Giegerich foldr steht für “fold right” LambdaExpressions 1 2 3 4 foldr :: ( a -> b -> b ) -> b -> [ a ] -> b foldr _ e [] = e foldr f e ( x : xs ) = f x ( foldr f e xs ) 5 6 7 8 9 10 sum prod and or length = = = = = foldr foldr foldr foldr foldr (+) 0 (*) 1 (&&) True (||) False (\ a b -> 1 + b ) 0 Gestaffelte Funktionen Higher Order Functions Noch mehr Beispiele mit foldr ...? Universität Bielefeld Programmieren in Haskell Giegerich LambdaExpressions Gestaffelte Funktionen 1 2 x ++ y = foldr (:) y x concat = foldr (++) [] Higher Order Functions foldr — sum aus der Vogelperspektive Universität Bielefeld foldr f e ersetzt alle : durch f und [] durch e. Programmieren in Haskell Example Giegerich [1..5] = [1,2,3,4,5] = 1:2:3:4:5:[] : 1 LambdaExpressions + 1 : 2 : 3 ⇒ foldr (+) 0 : 4 : 5 [] Gestaffelte Funktionen Higher Order Functions + 2 + 3 + 4 + 5 0 foldr — prod aus der Vogelperspektive Universität Bielefeld Programmieren in Haskell ∗ : Giegerich 1 2 ∗ 1 : Gestaffelte Funktionen ∗ 2 : LambdaExpressions Higher Order Functions ⇒ 3 4 ∗ 3 : ∗ 4 : 5 [] 5 1 → (1 ∗ (2 ∗ (3 ∗ (4 ∗ (5 ∗ 1))))) (6) foldr — length Universität Bielefeld : Programmieren in Haskell + Giegerich 1 : 2 + 1 : LambdaExpressions Gestaffelte Funktionen + 1 Higher Order Functions ⇒ 3 : 4 + 1 : 5 [] 1 + 1 0 → (1 + (1 + (1 + (1 + (1 + 0))))) (7) “Rechenstrategie” Vogelperspektive Universität Bielefeld Programmieren in Haskell Giegerich Vogelperspektive bzgl. f bedeutet: Rechne so viel (und NUR so viel) bis alle Auftreten von f verschwunden sind. das ist i.A. werder inner- noch outermost aber auf jeden Fall mathematisch korrekt. Dient nicht dem wirklichen Rechnen, aber dem Programmverstehen. LambdaExpressions Gestaffelte Funktionen Higher Order Functions foldr klammert von rechts ... Universität Bielefeld Programmieren in Haskell Giegerich LambdaExpressions 1 2 3 length " acbde " = > ( 1 + ( 1 + ( 1 + ( 1 + ( 1 + 0 ) ) ) ) ) and [ True , False , True ] = > True && ( False && ( True && False )) ... geht’s auch von links her? Gestaffelte Funktionen Higher Order Functions foldl Universität Bielefeld Programmieren in Haskell : f 4 f 3 f f e 1 2 1 5 f : 2 foldl ⇐ Giegerich f 1 : 3 foldr ⇒ : 4 : 5 [] LambdaExpressions f Gestaffelte Funktionen Higher Order Functions f 2 f 3 4 f 5 e foldl-Definition Universität Bielefeld Programmieren in Haskell Giegerich LambdaExpressions 1 2 3 foldl :: ( b -> a -> b ) -> b -> [ a ] -> b foldl f e [] = e foldl f e ( x : xs ) = foldl f ( f e x ) xs Der Endwert e ist jetzt ein Startwert. Gestaffelte Funktionen Higher Order Functions Unterschiede foldl, foldr Universität Bielefeld Programmieren in Haskell Giegerich Unterschiedliche Faltung LambdaExpressions foldr klammert rechtsassoziativ: ((a1 · (a2 · (a3 · . . . · (an · e))))) (8) Gestaffelte Funktionen Higher Order Functions foldl klammert linksassoziativ: (((((e · a1 ) · a2 ) · a3 ) · . . .) · an ) (9) die Faltung von foldr entspricht der Datentypdefinition ggf. Platzbedarf besser bei foldl Unterschiede foldl, foldr Universität Bielefeld Programmieren in Haskell Giegerich LambdaExpressions Eigenschaft 1 foldr f e ( reverse x ) = foldl ( flip f ) e x Beispiel siehe Tafel. Gestaffelte Funktionen Higher Order Functions foldl Beispiele Universität Bielefeld Programmieren in Haskell Giegerich LambdaExpressions sum = foldr (+) 0 or = foldr (||) False concat = foldl (++) [] ... Gestaffelte Funktionen Higher Order Functions Fold im Allgemeinen Universität Bielefeld Programmieren in Haskell Giegerich Fold-Operationen verarbeiten rekursive Datentypen nach deren Rekursionsschema, also mit struktureller Rekursion Kompakter Code Vermeidung von Redundanz Angabe des Wesentlichen Vermeidung von Fehlern bei hand-programmierter Rekursion LambdaExpressions Gestaffelte Funktionen Higher Order Functions Weitere Beispiele? Universität Bielefeld Programmieren in Haskell Giegerich 1 2 3 4 Die Funktion tabulate verwandelt Funktionen über einem LambdaExpressions gegebenen Intervall in tabellierende Funktionen Gestaffelte > tabulate :: ( Int , Int ) - >( Int - > a ) - >( Int - > a ) Funktionen Higher Order > tabulate ( lo , up ) f = ( t !) where Functions > t = array ( lo , up ) ( zip domain ( map f domain )) > domain = [ lo .. up ] 1 2 > g = tabulate f was sich beim mehrfachen Aufruf von g anstelle von f bezahlt macht. Combinator Languages, eDSLs Universität Bielefeld Programmieren in Haskell Funktionen höherer Ordnung, deren Argumente nur Funktionen sind, nennt man “Kombinatoren”. In manchen Anwendungen gibt es eine Anzahl komplexer, spezifischer Operationen, die sich zu Kombinatoren abstrahieren lassen. Beispiele: Parsing, Prettyprinting, Dynamic Programming, Format-Transformationen Dies führt zur Definition von anwendungs-spezifischen Sprachen, “eingebettet” in Haskell (embedded domain specific languages, eDSLs) Dies ist der schnellste Weg zu einer DSL, aber nicht der Weg zur schnellsten DSL Giegerich LambdaExpressions Gestaffelte Funktionen Higher Order Functions Zusammenfassung Universität Bielefeld Programmieren in Haskell Giegerich Wann definiert man selbst neue Funktioen höherer Ordnung? Wenn sich die gleiche Verknüpfung anderer Funktionen mehrfach wiederholt, ... ... macht die Verwendung von Kombinatoren die Systematik transparent, hilft beim Vermeiden von Fehlern, und erhöht so die eigene Programmier-Produktivität LambdaExpressions Gestaffelte Funktionen Higher Order Functions