Programmiersprachen Sprachen vs. Paradigmen Fragen: Paradigma: [griechisch] das, Wissenschaftstheorie: von T.S. Kuhn eingeführter Begriff, der die Gesamtheit aller eine wissenschaftliche Disziplin in einem Zeitabschnitt beherrschenden Grundauffassungen (z.B. Raum-, Zeitvorstellungen, methodologische Regeln) bezeichnet und somit festlegt, was als wissenschaftlich befriedigende Lösung angesehen werden kann. • Seit wann gibt es Programmiersprachen? ... • Wieviele Programmiersprachen gab bzw. gibt es? ... (aus: Brockhaus-Wissen, 2004) • Welche davon muss man als INF, ING-INF, . . . kennen? ... c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 1 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), Programmierparadigmen 2 Programmierparadigmen • Ein Programmierparadigma ist das einer Programmiersprache . . . oder Programmiertechnik zugrundeliegende Prinzip. (vgl. http://de.wikipedia.org/wiki/Programmierparadigma) ’klassische’ Programmierparadigmen: • imperativ • funktional • Unterschiedliche Paradigmen führen zu unterschiedlichen Sichten auf Programmierprobleme und damit oft zu unterschiedlichen Lösungen. • logisch • objektorientiert Anmerkung: • konkrete Programmiersprachen sind oft Mischformen (hybrid) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 3 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 4 Programmierparadigmen Programmierparadigmen aktuelle Entwicklungen: Wie eignet man sich die unterschiedlichen Programmierparadigmen am besten an? • agentenorientierte Programmierung • aspektorientierte Programmierung • ... c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 5 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), Programmierparadigmen 6 Programmierparadigmen Wie eignet man sich die unterschiedlichen Programmierparadigmen am besten an? Lernziele: • Denk- und Herangehensweisen • Kennenlernen von Prinzipien und Konzepten • adäquate Programmierstile • Kennenlernen von prototypischen Vertretern, also von konkreten Programmiersprachen, die Prinzipien des Paradigmas besonders klar umsetzen • konzeptueller Rahmen für Klassifikation, Bewertung und Aneignung konkreter Programmiersprachen • eigenständiges Lösen von Problemen und Aufgaben • Vergleiche und Diskussion von Alternativen c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 7 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 8 »Warum funktionale Sprachen studieren?« »Warum funktionale Sprachen studieren? « • didaktisches Argument: verbessertes Verständnis dessen, was Programmieren eigentlich ist ganz neue Aspekte gegenüber ‘traditionellen’ Sprachen • praktische Relevanz: Produktivität der Programmierer wird gesteigert, Softwarekosten werden gesenkt (Hinweis: ERLANG von Ericson; XSLT, XQuery ) • theoretische Fundierung: ‘Informatiker zeichnet aus, dass sie das Prinzip des Programmierens und das Prinzip des Sich-in-Programmiersprachen-Ausdrückens beherrschen’ [Pepper 1998] c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 9 »Klassifikation von Programmiersprachen: « [E.W.Dijkstra, 1995] c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 10 »Charakteristika (vgl. [Pepper 1998]) « funktional Programm ist Ein-/Ausgaberelation; diese Abbildung wird im Programmtext als Funktion geschrieben • imperative Programmiersprachen: – ‘klassische’: COBOL, FORTRAN, PASCAL, C, ... – objektorientierte: SMALLTALK, C++, JAVA, ... Programme ‘zeit-los’ • deklarative Programmiersprachen: – logische: PROLOG, ... – funktionale: LISP, SCHEME, ML, MIRANDA, OPAL, GOFER, HASKELL, ... c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), The clarity and economy of expression that the language of functional programming permits is often very impressive, and, but for human inertia, functional programming can be expected to have a brilliant future. 11 Programme auf abstraktem, math. orientiertem Niveau imperativ Programm ist Arbeitsanweisung für Maschine; Ein-/Ausgabeverhalten aus Arbeitsweise der Maschine analysierbar ‘Zustand’ der ausführenden Maschine beeinflusst Programm Programme konkret auf Maschinen bezogen formuliert c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 12 »Haskell « »Programmiersprache Haskell:« • benannt nach Haskell B. Curry Programmierumgebung Hugs • einer der Pioniere des λ-Kalkül • Haskell Users Gofer System • erste Spezifikation der Sprache Ende 80er Jahre • frei erhältlicher Interpreter • aktuelle Version: Haskell 98 • für alle gängigen Plattformen • Download, Tutorials, usw.: http://www.haskell.org/ c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 13 »funktionale Programme in Haskell « c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 14 »funktionale Programme in Haskell cont. « • Definitionen von Funktionen und anderen Werten durch Gleichungen • lies ‘::’ als ‘hat Typ’ oder ‘ist vom Typ’ • Beispiele: • Definition assoziiert Namen (Identifikator) mit Wert eines bestimmten Typs size :: Int size = 12 + 13 • Syntax: square :: Int -> Int square n = n * n <name> :: <type> <name> = <expression> c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 15 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 16 zwei Arten von Dateistilen: zwei Arten von Dateistilen: cont • Skripte (Extension ‘.hs’): • literate Skripte (Extension ‘.lhs’): alles ist Programmtext, sofern nicht explizit als Kommentar gekennzeichnet – Kommentare bis Zeilenende eingeleitet durch zwei aufeinanderfolgende ‘-’ – alles ist Kommentar, sofern nicht am Zeilenanfang durch ‘>’ als Programmzeile gekennzeichnet – literat . . . ‘wörtlich’ – Abschnittskommentare zwischen ‘{-’ und ‘-}’ c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 17 Beispiel eines Skripts c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 18 Beispiel eines literaten Skripts {- Die Berechnung der Funktion Fakultät ist ein Standardbeispiel fuer Rekursion. ... mehrere Zeilen Kommentartext ... -} -- Berechnung der Fakultät mit Konditional if > fak :: Int -> Int > fak n = if n == 0 then 1 else n * fak (n - 1) fak :: Int -> Int Eine Variante mit Pattern-Matching: fak n = if n == 0 then 1 else n * fak (n - 1) > fak 0 = 1 > fak n = n * fak (n - 1) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 19 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 20 Haskell – Sprachelemente Haskell – Sprachelemente • vordefinierte elementare Typen (auch Sorten genannt) für Konstante (= nullstellige Funktionen): • Typisierung: jedes Objekt in Haskell hat einen wohldefinierten Typ • Zweck der Typisierung: Bool Int Char Float Integer Rational Double – frühzeitiges Erkennen von Programmierfehlern (type checking) – schon vor Programmausführung (statische Analyse) • durch Deklaration mit type lassen sich benutzerdefinierte Typen einführen c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 21 Zur Unterscheidung zwischen Int und Integer c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 22 Typisierung cont. • Zur Klasse Int gehören ganze Zahlen, die sich mit einer festen Zahl von Bytes darstellen lassen. • Typ von Funktionen (auch Funktionalität genannt) : Definitions- und Wertebereich durch -> getrennt angegeben • der Wert der Variablen maxBound::Int gibt die grösste als Int darstellbare ganze Zahl an. Dieser Wert ist 2147483647. • Beispiel: double :: Int -> Int double n = 2*n • Will man beliebig grosse ganze Zahlen verarbeiten, so sollte man den Typ Integer verwenden. • bei mehreren Argumenten werden deren Typen durch -> verbunden • Beispiel: max mit 2 Argumenten aus Int und Wert aus Int max :: Int -> Int -> Int c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 23 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 24 Typisierung cont. vordefinierte arithmetische Operatoren • Interpretation einer Typdeklaration wie scale :: Picture -> Int -> Picture • + . . . Summe zweier Zahlen • * . . . Produkt zweier Zahlen • erstens: scale hat zwei Argumente: das erste ist vom Typ Picture, das zweite vom Typ Int • ˆ . . . Exponentiation: 2 ˆ 3 gibt 8 • zweitens: das Ergebnis der Anwendung von scale ist vom Typ Picture • - ... – Differenz, wenn infix verwendet; – umgekehrtes Vorzeichen bei Präfixverwendung (vgl. negate) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 25 vordefinierte arithmetische Operatoren cont. c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 26 vordefinierte Vergleichsoperatoren • div . . . ganzzahlige Division • für ganze Zahlen, d.h. Typ Int -> Int -> Bool: >, >=, ==, / =, <=, < • mod . . . Rest bei ganzzahliger Division (modulo) • abs . . . Absolutbetrag • diese Vergleichsoperatoren sind – wie auch die arithmetischen Operatoren – ‘überladen’ und auch auf Float anwendbar • negate . . . ändere Vorzeichen • Typ dann: Float -> Float -> Bool • für == gilt auch Bool -> Bool -> Bool bzw. sogar allgemein t -> t -> Bool, sofern für den Typ t Gleichheit definiert (Hinweis: t hier Typvariable) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 27 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 28 einige vordefinierte Operatoren bzw. Konstanten für Float Name(n) + - * / ˆ ** exp log logBase pi signum sqrt cos, sin, tan acos, asin, atan ceiling, floor, round fromInt Typ Float -> Float -> Float Float -> Int -> Float Float -> Float -> Float Float -> Float Float -> Float Float -> Float -> Float Float Float -> Float Float -> Float Float -> Float Float -> Float Float -> Int Int -> Float Bem. • werden infix verwendet, d.h. 3 + 4 aber: Verwendung eines Operatorsymbols <op> in Präfixposition möglich mit Notation (<op>) , d.h. (+) 3 4 == 3 + 4 xn xy ex ln x loga x π • können assoziativ sein; z.B. +, * • nicht-assoziative Operatoren werden festgelegt als links-assoziativ oder rechts-assoziativ z.B. - links-assoziativ, d.h. a - b - c == (a - b) - c Rundung Konversion c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), Operatoren 29 Operatoren und Funktionen • Operatoren haben Bindungsstärke oder Fixität (engl. fixity) z.B. * hat Fixität 7, + hat 6, ˆ hat 8, daher a + b * c == a + (b * c) und a ˆ b * c == (a ˆ b) * c c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 30 Konversionen von Operatoren und Funktionen • Funktionsanwendung hat höchste Bindungsstärke • allgemeine Schreibweise: Funktionsname vor Argumente(e) f v1 v2 ...vn • Beachte: da Funktionsanwendung höhere Bindung als jeder andere Operator wird f n+1 interpretiert als (f n)+1 • werden Infix-Operatoren in Klammern eingeschlossen, so können sie als Funktionen vor ihren Argumenten verwendet werden Beispiel: (+) :: Int -> Int -> Int Verwendung: (+) a b == a + b • Funktionen können zu Operatoren gemacht werden durch Einschluss des Funktionsnamen in sog. Backquotes • für andere Interpretation ist explizite Klammerung notwendig: f (n+1) a ‘max‘ b == max a b c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 31 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 32 Funktionen in Haskell: Definition von Funktionen • mit Gleichungen • Funktionen sind in funktionalen Programmiersprachen ‘Bürger erster Klasse’ (‘first class citizens’) • dabei bedingte Ausdrücke verwendbar • sie unterscheiden sich nicht von Daten, max :: Int -> Int -> Int max x y = if x >= y then x else y • sie können Argumente von Funktionen sein, • gegebenenfalls verschachtelte bedingte Ausdrücke • sie können Werte von Funktionen sein, • sie können Elemente in zusammengesetzten Datenstrukturen sein (z.B. Listen, Tupel, . . . ). c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 33 fib :: Int -> Int fib x = if x == 0 then 0 else if x == 1 then 1 else fib (x - 1) + fib (x - 2) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), Definition von Funktionen cont. Definition von Funktionen cont. • Variante mit ‘Wächtern’ (engl. ‘guards’) • Beispiele cont.: • boolesche Ausdrücke für Fälle in einer Definition fib fib | | | • idealerweise disjunkt und vollständig abdeckend • Beispiele: fib x | x == 0 || x == 1 | otherwise = x = y c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), :: Int -> Int x x == 0 = 0 x == 1 = 1 x > 1 = fib (x - 1) + fib (x - 2) • Variante: max :: Int -> Int -> Int max x y | x >= y | otherwise 34 35 = x = fib (x - 1) + fib (x - 2) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 36 Definition von Funktionen cont. Definition von Funktionen cont. • Variante mit ‘Pattern matching’ • Variante mit ‘Pattern matching’ • Beispiel: fib :: Int -> Int fib 0 = 0 fib 1 = 1 fib x = fib (x - 1) + fib (x - 2) mistery :: Int -> Int -> Int mistery 0 y = y mistery x y = x • ‘guards’ bzw. Gleichungen werden sequentiell ausgewertet c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 37 Definition von Funktionen cont. 38 Operationen mit Funktionen • Verwendung von ‘wildcards’ in Mustern möglich • Definition von Funktionen • Anwendung von Funktionen auf Argumente mistery :: Int -> Int -> Int – Argumente können verschachtelt wieder Anwendungen von Funktionen auf Argumente sein mistery 0 y = y mistery x _ = x • ‘wildcards’ immer dann sinnvoll, wenn beliebige Werte im Muster (auf der linken Seite) zugelassen werden sollen und auf diese auf der rechten Seite der Gleichung nicht verwiesen werden muss c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 39 • partielle Anwendung von Funktionen • Funktionskomposition c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 40 Haskell – lexikalische Konventionen Haskell – lexikalische Konventionen • Identifikatoren: Bezeichner von Variablen, von Typen von Funktionen usw. • Identifikatoren müssen in Haskell mit einem Buchstaben beginnen • dieser kann von einer beliebigen Sequenz von Buchstaben, Ziffern, Unterstrichen ( ) und einzelnen Anführungszeichen gefolgt sein Konventionen für führende Großbuchstaben bzw. Kleinbuchstaben • alle Namen, die in Definitionen von Werten verwendet werden (d.h. also auch alle Funktionsnamen), müssen mit einem kleinen Buchstaben beginnen, ebenso die Bezeichner für Variable und Typvariable • Beachte: Verwendung von – einzeln: . . . – als erstes Zeichen gefolgt von Sequenz: . . . • mit einem Großbuchstaben beginnen Typnamen (z.B. Int), Konstruktoren, True und False, Modulnamen und auch die Namen von Typklassen c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 41 Haskell – lexikalische Konventionen 42 Haskell – lexikalische Konventionen kleine Sammlung sog. reservierter Wörter • Empfehlung: mnemonische, ’sprechende’ Namen verwenden • können nicht als Identifikatoren benutzt werden • z.B. zinssatz statt nur z • dazu zählen: • Empfehlung: bei Namen, die aus mehreren Worten zusammengesetzt sind, den Beginn des zweiten Worts (und ggf. folgender Wörter) in Großbuchstaben schreiben case|class|data|default|deriving|do|else |if|import|in|infix|infixl|infixr|instance |let|module|newtype|of|then|type|where|_ c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), • z.B. letztesElement 43 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 44 Listen in Haskell: Primitive Rekursion über Listen • Listen . . . Zusammenfassung beliebig vieler Elemente vom selben Typ • auch ‘tail recursion’ genannt • Darstellung des Listentyp: [<typ>], z.B. • Definition durch – Angabe des Ausgangswerts für [] und – Angabe, wie von Wert für xs auf Wert von (x:xs) übergegangen wird [Int], [Char], [[Int]], [(Name, Matrnr)], ... • Listen werden – sofern ungleich der leeren Liste [] - durch Anwendung des Konstruktors ‘:’ in eindeutiger Weise ausgehend von [] aufgebaut • Beispiel: length [] = 0 length (x:xs) = 1 + length xs • Beispiel: [4, 2, 3] == 4:2:3:[] == 4:(2:(3:[])) • ‘:’ ist rechts-assoziativ, d.h. x:y:zs = x:(y:zs) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 45 Primitive Rekursion über Listen c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 46 Rekursion über Listen cont. Beachte: • Beispiel: Summe der Werte der Listenelemente • length ist polymorph, d.h. auf Listen unterschiedlichen Typs anwendbar: length :: [a] -> Int • ’wildcard’ in der zweiten Gleichung der Definition möglich, d.h. length ( :xs) = 1 + length xs sum :: [Int] -> Int sum [] = 0 sum (x:xs) = x + sum xs • Frage: sum [2, 4 .. 11] ֒→ . . . ? • length ist wie viele andere Funktionen auf Listen im sog. StandardPrelude von Haskell bereits vordefiniert • sum ist vordefiniert und polymorph Prelude> :t sum sum :: Num a => [a] -> a c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 47 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 48 Rekursion über Listen cont. Der Typ String Beispiel: Sortieren mit Insertion-Sort • Spezialfall: Liste aus Zeichen, d.h. type String = [Char] iSort :: [Int] -> [Int] • alle polymorphen Listenfunktionen können für String benutzt werden iSort [] iSort (x:xs) = [] = ins x (iSort xs) • Ausgabe mit putStr putStr :: String -> IO () ins :: Int -> [Int] -> [Int] ins ins | | • Wechsel zwischen Strings und Werten: show (2 + 3) ==> "5" x [] = [x] x (y:ys) x<= y = x:(y:ys) otherwise = y: ins x ys c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), • umgekehrt: (read "True") :: Bool ==> True 49 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 50 Darstellung von Listen Darstellung von Listen • für Listen von Zahlen, Zeichen und anderen Aufzählungstypen gibt es Kurzschreibweisen • Variante: die ‘Differenz’ zwischen dem zweiten und dem ersten Element ergibt die ‘Schrittweite’ • [n .. m] Kurzform für [n,n+1,...,m] (falls m kleiner als n ist Liste leer) • Beispiele: [3,5 .. 14] [0.0,0.4 .. 2.0] [’f’,’h’ .. ’q’] • Beispiele: [3 .. 9] [1.2 .. 4.1] [’f’ .. ’q’] = [3,5,7,9,11,13] = [0.0,0.4,0.8,1.2,1.6,2.0] = "fhjlnp" == [3,4,5,6,7,8,9] == [1.2,2.2,3.2] == "fghijklmnopq" c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 51 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 52 Listenkomprehension Listenkomprehension cont.: Beispiele (vgl. [S.Thompson, 1999, pp.79]) • Vorbild: math. Notation für Mengen • Beispiel: Teiler(n) = {i∈Nk i≤n, i Teiler von n} • Sei ex die Liste [2,4,7] • in Haskell: teiler :: Int -> [Int] teiler n = [ i | i<-[1..n], mod n i == 0] • [ 2*n | n<-ex] ֒→ [4,8,14] • lies: ‘Nimm alle 2*n für n aus der Liste ex’ – Liste [1..n] wirkt als Generator – Test mod n i == 0 wählt Elemente aus • zur Veranschaulichung: Auswertung tabellarisch n 2*n • mehrere Tests in Form boolescher Ausdrücke möglich c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 53 = = 2 4 4 8 7 14 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), Listenkomprehension cont.: Beispiele 54 Listenkomprehension cont.: Beispiele (vgl. [S.Thompson, 1999, pp.79]) (vgl. [S.Thompson, 1999, pp.79]) • Sei isEven definiert: • links von <- können nicht nur Variable, sondern auch Muster (pattern) stehen isEven :: Int -> Bool isEven n = (n ‘mod‘ 2 == 0) • Beispiel: addPairs :: [(Int,Int)] -> [Int] addPairs pairList = [ n+m | (n,m) <- pairList] • [ isEven n | n<-ex] ֒→ [True, True, False] • Veranschaulichung mit Tabelle: • mehrere Tests möglich [ n+m n m n+m • [ 2*n | n<-ex, isEven n, n>3] c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 55 | (n,m) <- [(2,3),(2,1),(7,8)]] = 2 2 7 = 3 1 8 = 5 3 15 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 56 Listenkomprehension cont. Listenkomprehension cont.: (vgl. [S.Thompson, 1999, pp.79]) • Syntax: allgemeine Form ist [ e | q1, ... qk ] • zusätzlicher Test: • dabei ist jeder der Qualifikatoren qi • nur geordnete Paare addieren addOrdPairs :: [(Int,Int)] -> [Int] addOrdPairs pairList = [ n+m | (n,m) <- pairList, n < m] • wichtig: ein Ausdruck lExp bzw. bExp in qi kann auf die in q1 bis qi−1 benutzten Variablen verweisen • Frage: addOrdPairs [(2,3),(2,1),(7,8)] ֒→ . . . ? c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), – entweder ein Generator der Form p <- lExp mit p Muster und lExp Ausdruck vom Listentyp – oder ein Test, d.h. boolescher Ausdruck bExp 57 Listenkomprehension cont. c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), Listenkomprehension cont.: (vgl. [S.Thompson, 1999, 17.3]) • pythagoräische Tripel: für welche Tripel (x, y, z) ganzer Zahlen gilt: • mit mehreren Generatoren können Elemente aus zwei oder mehr Listen kombiniert werden x2 + y2 = z2 ? • als Listenkomprehension: • Beispiel: pyTriple n = [(x,y,z)| x<-[2 .. n], y<-[x+1 .. n], z<-[y+1 .. n], x*x+y*y==z*z] pairs :: [a] -> [b] -> [(a,b)] pairs xs ys = [ (x,y) | x<-xs , y<-ys ] • Frage: pyTriple 100 ֒→ ... ? pairs [1,2,3] [4,5] ֒→ [(1,4),(1,5),(2,4),(2,5),(3,4),(3,5)] c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 58 59 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 60 Tupel in Haskell: Tupel in Haskell: • Aggregationstyp Tupel: auch Tupel dienen – wie Listen – zum Zusammenfassen von Daten zu einem Objekt • in Tupeln wird eine feste Anzahl von Objekten aggregiert, die aber von unterschiedlichem Typ sein können • Unterschied: Liste aggregiert variable Anzahl von Objekten mit identischem Typ c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 61 Verwendung von Tupeln (vgl. [S.Thompson, 1999, 5.2]) – Name (Typ String) und – Matrikelnummer (Typ Int) type Student = (String,Int) • math. Sicht: Tupeltyp als Teilmenge des Kreuzprodukts der Komponententypen c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 62 Funktionen mit Tupeln • Tupel für zusammengesetzte Resultate von Funktionen; Beispiel: • meist mit Pattern matching definiert; Beispiel: minAndMax :: Int -> Int -> (Int, Int) minAndMax x y | x>=y | otherwise • Beispiel: Studenten seien dargestellt durch Paare aus addPair :: (Int, Int) -> Int addPair (x,y) = x+y = (y,x) = (x,y) • Variante mit vordefinierten Selektorfunktionen für Paare addPair :: (Int, Int) -> Int addPair p = fst p + snd p c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 63 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 64 Funktionen mit Tupeln Beispiel: effiziente Berechnung der Fibonacci-Zahlen (vgl. [S.Thompson, 1999, pp. 75]) • mögliche Definition dieser Selektorfunktionen: • Idee: Funktion, die zwei aufeinanderfolgende Werte der Folge als Resultat, d.h. gesucht ist fst (x,y) = x snd (x,y) = y fibPair n = (fib n, fib (n+1)) • in Prelude.hs: mit wildcards in Muster fst fst (x,_) :: (a,b) -> a = x snd snd (_,y) :: (a,b) -> b = y c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), • aus einem Paar (u,v) ergibt sich das nachfolgende als (v,u+v) über fibStep fibStep :: (Int, Int) -> (Int, Int) fibStep (u,v) = (v,u+v) 65 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), Beispiel: effiziente Berechnung der Fibonacci-Zahlen cont. Funktionen mit Tupeln 66 bei Definition von Funktionen auf Tupeln können auch verschachtelte Muster verwendet werden (vgl. [S.Thompson, 1999, 5.2]) • damit fibPair definierbar als fibPair :: Int -> (Int, Int) fibPair n | n== 0 = (0,1) | otherwise = fibStep (fibPair (n-1)) shift :: ((Int,Int),Int) -> (Int,(Int,Int)) shift ((x,y),z) = (x,(y,z)) • schliesslich: das erste Element des Paars ist das Ergebnis fastFib :: Int -> Int fastFib = fst . fibPair c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 67 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 68 Funktionale Abstraktion: Beispiele rekursiv definierter Funktionen cont. Beispiele rekursiv definierter Funktionen (vgl. [Rabhi & Lapalme, 1999, 1.2.2]) • Summe der natürlichen Zahlen bis n • Fakultätsfunktion fact n | n == 0 | n > 0 • ... sumInt n | n == 0 | n > 0 = 1 = n * fact(n-1) = 0 = n + sumInt(n-1) • Summe der Quadrate der natürlichen Zahlen bis n • ... sumSqr n | n == 0 | n > 0 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 69 Beispiele . . . cont. = 0 = n*n + sumSqr(n-1) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 70 Darstellung dieser Abstraktion in Haskell das den obigen Beispielen zugrundeliegende Rekursionsprinzip kann wie folgt abstrahiert werden: • es wird Funktionswert für die Basis (d.h. für n == 0) definiert induction base comb n | n == 0 = base | n > 0 = comb n (induction base comb (n-1)) Frage: Typ von induction ? • der Funktionswert für n ergibt sich durch Kombination von n (bzw. eines aus n berechneten Werts) mit dem Funktionswert für (n-1) • m.a.W.: der Rekursionsschritt wird als Anwendung einer zweistelligen Kombinationsfunktion auf n und Funktionswert für (n-1) realisiert c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 71 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 72 Abstraktion: induction Abstraktion: induction • induction ist Funktion höherer Ordnung (nimmt eine Funktion als Argument; hier: comb) • damit Darstellung von fact bzw. sumInt mit Hilfe von induction und der vordefinierten Funktionen (+) bzw. (*) > fact n = induction 1 (*) n > sumInt n = induction 0 (+) n c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 73 Benannte vs. anonyme Funktionen > fact = induction 1 (*) > sumInt = induction 0 (+) • die rechten Seiten dieser Gleichungen zeigen sog. partielle Anwendungen von mehrstelligen Funktionen c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 74 Benannte vs. anonyme Funktionen • die Kombinationsfunktion für sumSqr könnte benannt definiert werden, z.B. f x y = x*x + y • dann: • alternativ: Kombinationsfunktion als sog. anonyme Funktion • Syntax in Anlehnung an den λ-Kalkül mit (λxy.x ∗ x + y): sumSqr n = induction 0 (\x y -> x*x + y) n > sumSqr n = induction 0 f n bzw. > sumSqr = induction 0 f c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), • alternativ: Definition auf Funktionsebene möglich, d.h. bzw. sumSqr = induction 0 (\x y -> x*x + y) 75 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 76 Funktionen höherer Ordnung: Die Funktion map Programmiermuster: Anwendung einer Funktion auf jedes Element einer Liste; Ergebnis ist die Liste der Funktionswerte zu den Elementen Funktionen sind Funktionen höherer Ordnung, wenn sie • eine Funktion als Argument nehmen und/oder mögliche Definitionen: • eine Funktion als Wert zurückgeben. • als Listenkomprehension: Wiederkehrende Programmiermuster (pattern) lassen sich häufig als Funktionen höherer Ordnung abstrahieren. map f xs = [ f x | x <- xs] • direkt: map f [] map f x:xs c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 77 Die Funktion map cont. c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 78 Programmiermuster Filtern: Wiederkehrende Frage: welche Elemente einer Liste genügen einer gewünschten Eigenschaft? • Typ von map map :: (a -> b) -> [a] -> [b] • Darstellung von Eigenschaften als Prädikate, d.h. als Funktionen vom Typ t -> Bool für bel. Typ t • Beispiele: • Funktion filter nimmt ein Prädikat und eine Liste als Argument und gibt die Liste derjenigen Elemente zurück, für die das Prädikat zutrifft doubleAll xs = map double xs double x = 2 * x • mögliche Definition als Listenkomprehension: convertChrs :: [Char] -> [Int] convertChrs xs = map ord xs c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), = [] = f x : map f xs filter p xs = [x | x <- xs, p x] 79 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 80 Programmiermuster Filtern: Elementweises Verbinden von Listen: zip • mögliche Definition direkt: • zip: aus Paar von Listen mache Liste mit Paaren korrespondierender Elemente; ignoriere „überschüssige“ Elemente ohne korrespondierenden Partner filter p [] = [] filter p x:xs = if p x then x:filter p xs else filter p xs • • Typ von filter: • Beispiel: filter :: ( a -> Bool) -> [a] -> [a] c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), zip :: [a] -> [b] -> [(a,b)] zip [4,7,1,1] "Koeln" = [(4,’K’),(7,’o’),(1,’e’),(1,’l’)] 81 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 82 Elementweises Verbinden von Listen: zipWith Anonyme Funktionen • Verallgemeinerung zipWith: verknüpfe die korrespondierenden Elemente mit einer zweistelligen Funktion • Funktionen als Argumente von Funktionen höherer Ordnung können einerseits durch ihren Namen referenziert werden zipWith f (x:xs) (y:ys) = f x y : zipWith f xs ys zipWith f _ _ = [] • wird eine Funktion nur als Argument bei einer Anwendung einer Funktion höherer Ordnung benötigt (und nirgends sonst), so reicht oft auch eine sog.anonyme Funktion aus • Beispiel: • die Darstellung anonymer Funktionen erfolgt durch einen LambdaAusdruck mit der Syntax: (\<Var(s)> -> <Körper>) sideBySide p1 p2 = zipWith (++) p1 p2 • Typ? : zipWith :: • Beispiele: (\x -> x*x) oder (\x y --> x*x - 2*x*y + y*y) ................................... c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 83 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 84 Funktionen in Prelude.hs Funktionen in Prelude.hs Prelude.hs enthält viele nützliche Funktionen Funktionen, die ein Paar als Argument nehmen, vs. Funktionen mit zwei Argumenten /usr/local/hugs98-Dec2001/share/hugs/lib/Prelude.hs c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 85 Funktionen in Prelude.hs cont. id id curry curry f x y :: ((a,b) -> c) -> (a -> b -> c) = f (x,y) uncurry uncurry f p :: (a -> b -> c) -> ((a,b) -> c) = f (fst p) (snd p) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), Listenfunktionen: Standard list functions (PreludeList) :: a -> a = x head head (x:_) :: [a] -> a = x const const k _ :: a -> b -> a = k last last [x] last (_:xs) :: [a] -> a = x = last xs (.) (f . g) x :: (b -> c) -> (a -> b) -> (a -> c) = f (g x) tail tail (_:xs) :: [a] -> [a] = xs flip flip f x y :: (a -> b -> c) -> b -> a -> c = f y x init init [x] init (x:xs) :: [a] -> [a] = [] = x : init xs x c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 86 87 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 88 Listenfunktionen cont. null null [] null (_:_) :: [a] -> Bool = True = False (++) [] ++ ys (x:xs) ++ ys :: [a] -> [a] -> [a] = ys = x : (xs ++ ys) map map f xs :: (a -> b) -> [a] -> [b] = [ f x | x <- xs ] filter filter p xs :: (a -> Bool) -> [a] -> [a] = [ x | x <- xs, p x ] c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), Listenfunktionen cont. concat concat :: [[a]] -> [a] = foldr (++) [] length length :: [a] -> Int = foldl’ (\n _ -> n + 1) 0 (!!) (x:_) (_:xs) (_:_) [] 89 !! !! !! !! :: [a] -> Int -> a 0 = x n | n>0 = xs !! (n-1) _ = error "Prelude.!!: negative index" _ = error "Prelude.!!: index too large" c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), Funktionen in Prelude.hs cont.: Faltung 90 Funktionen in Prelude.hs cont. Fehlererzeugung: foldl :: (a -> b -> a) -> a -> [b] -> a foldl f z [] = z foldl f z (x:xs) = foldl f (f z x) xs primitive error foldr :: (a -> b -> b) -> b -> [a] -> b foldr f z [] = z foldr f z (x:xs) = f x (foldr f z xs) Beispiel einer Kontrollstruktur: until until p f x ... c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), :: String -> a 91 :: (a -> Bool) -> (a -> a) -> a -> a = if p x then x else until p f (f x) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 92 Prinzip der strukturellen Induktion für Listen: Beispiel für Beweis einer Programmeigenschaft um zu beweisen, dass eine logische Eigenschaft P(xs) für alle endlichen Listen xs gilt, sind zwei Dinge zu tun • Induktionsanfang oder -basis: Beweise P([]) direkt • Induktionsschritt: Beweise P(x:xs) unter der Annahme, dass P(xs) gilt – m.a.W.: es ist zu zeigen, dass P(xs) ⇒ P(x:xs) – dabei: P(xs) . . . Induktionshypothese • Gegeben seien die folgenden Definitionen: sum :: [Int] -> Int sum [] = 0 sum (x:xs) = x + sum xs (sum.1) (sum.2) doubleAll :: [Int] -> [Int] doubleAll [] = [] doubleAll (z:zs) = 2*z : doubleAll zs (doubleAll.1) (doubleAll.2) • Behauptung: für alle endlichen Listen xs ganzer Zahlen gilt: sum (doubleAll xs) = 2*sum xs c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 93 Beweis einer Programmeigenschaft cont. • Behauptung: • Induktionsbasis: Beweise, dass length (xs ++ ys) = 2 * sum[] (base) • Induktionsschritt: Beweise, dass sum (doubleAll (x:xs)) = 2 * sum (x:xs) (ind) = length xs + length ys • Dabei gelten die folgenden Definitionen: length [] length (z:zs) = 0 = 1 + length zs [] ++ zs = zs (w:ws) ++ zs = w : (ws ++ zs) unter der Induktionshypothese sum (doubleAll xs) 94 weitere Beispiele für Induktionsbeweise Struktur des Beweises: sum (doubleAll []) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), = 2 * sum xs c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), (length.1) (length.2) (++.1) (++.2) (hyp) 95 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 96 weitere Induktionsbeweise cont. weitere Induktionsbeweise cont. Struktur des Beweises: • Sei reverse definiert durch: • Induktionsbasis: Beweise, dass length ([] ++ ys) = length [] + length ys (base) reverse [] reverse (z:zs) = [] = reverse zs ++ [z] (reverse.1) (reverse.2) • Behauptung: für alle endlichen Listen xs und ys gilt • Induktionsschritt: Beweise, dass length ((x:xs) ++ ys) = length (x:xs) + length ys (ind) reverse (xs ++ ys) = reverse ys ++ reverse xs (reverse++) unter der Induktionshypothese length (xs ++ ys) = length xs + length ys (hyp) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 97 weitere Induktionsbeweise cont. c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 98 Ausführung des Beweises Struktur des Beweises für Beziehung (reverse++): • Induktionsbasis: Beweise, dass .................................................... • Induktionsschritt: Beweise, dass .................................................... unter der Induktionshypothese .................................................... c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 99 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 100 Funktionen höherer Ordnung: Falten einer Funktion f cont. Falten einer Funktion f in eine nichtleere Liste In Haskell: Fälle: foldr1 f [x] • Falten von f in eine einelementige Liste [x] ergibt x foldr1 f (x:xs) = f x (foldr1 f xs) = x Typ: • Falten von f in eine längere Liste entspricht foldr1 :: (a -> a -> a) -> [a] -> a foldr1 f [e1, e2, ... , ek] = e1 ‘f‘ (e2 ‘f‘ ( ... ‘f‘ ek) ... ) = e1 ‘f‘ (foldr1 f [e2, ... , ek]) = f e1 (foldr1 f [e2, ... , ek]) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 101 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), Falten einer Funktion f cont. Falten einer Funktion f cont. Beispiele: foldr1 (+) [4,7,1,1] = 13 foldr1 (||) [False, True, False] = True Verallgemeinerung: durch Angabe eines Werts s für die leere Liste lässt sich eine für alle endlichen Listen anwendbare Faltungsfunktion definieren foldr1 (++) ["Dies ", "ist ", "ein ", "Beispiel!"] = "Dies ist ein Beispiel!" foldr1 max [4,7,1,1] = 7 foldr1 min [4,7,1,1] = 1 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 102 103 foldr f s [] = s foldr f s x:xs = f x (foldr f s xs) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 104 Falten einer Funktion f cont. Beispiele für mit foldr definierbare Standardfunktionen von Haskell Typ von foldr: concat :: [[a]] -> [a] concat xs = foldr (++) [] xs foldr :: (a -> a -> a) -> a -> [a] -> a and :: [Bool] -> Bool and bs = foldr (&&) True bs falls Startwert von Typ b: foldr :: (a -> b -> b) -> b -> [a] -> b rev :: [a] -> [a] rev xs = foldr snoc [] xs snoc :: a -> [a] -> [a] snoc x xs = xs ++ [x] c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 105 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), foldr1 vs. foldr Beispiele . . . cont. Frage: Lässt sich foldr1 mit foldr definieren? Wenn ja, wie und unter welchen Bedingungen an f? iSort :: [Int] -> [Int] iSort xs = foldr ins [] xs ins ins | | 106 foldr1 x [] = [x] x (y:ys) x<= y = x:(y:ys) otherwise = y: ins x ys ................................... ................................... ................................... c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 107 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 108 Funktionen als Werte: Funktionen als Werte: Ein ungewohnter Gedanke: . . . mit weitreichenden Konsequenzen: . . . I was extremely ill-equipped to appreciate functional programming when I encountered it: I was, for instance, totally baffled by the shocking suggestion that the value of a function could be another function. . . . Die Möglichkeit, Funktionen auf vielfältige Weise mittels anderer Funktionen miteinander zu verknüpfen, eröffnet Programmiermethoden, die in ihrer Eleganz und Ausdrucksstärke weit über das hinausgehen, was uns aus der traditionellen imperativen Programmierung vertraut ist. Man kann mit Fug und Recht sagen, dass erst diese Techniken die funktionale Programmierung zu dem machen, was sie ist: eine extrem elegante Form, Algorithmen auszudrücken. [E.W. Dijkstra; zitiert in: Pepper 1999] [Pepper 1999, p.89] c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 109 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 110 Aber: Daher: . . . Dieser Programmierstil ist für viele ungewohnt, und weil Unvertrautes erst einmal verunsichert, ist die erste Reaktion häufig Ablehnung. . . . Und erhöhte Abstraktion geht grundsätzlich mit erhöhter intellektueller Herausforderung einher. Etwas sarkastisch ausgedrückt: Wo man sich in C oder BASIC mit fleißigem Testen und Debugging irgendwie zum Ziel durchwursteln kann, muss man hier nachdenken. Und erstaunlicherweise findet manch einer Letzteres abschreckender als Ersteres. . . . ein objektives Problem: Auf dieser Ebene (mehr oder weniger) virtuos mit Funktionen zu spielen, erhöht zweifellos den Abstraktionsgrad, auf dem Problemlösen stattfindet. [Pepper 1999, p.90] [Pepper 1999, p.90] c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 111 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 112 Funktionen als Werte: Funktionen als Werte: cont. mirror :: (Float -> Float) -> (Float -> Float) Beispiel: Manipulation beliebiger reeller Funktionen (vgl. [Pepper 1999, 8.2.2]) mirror f x = f (-x) Sei f :: Float -> Float beliebig. stretch :: Float -> (Float -> Float) -> (Float -> Float) Dann: stretch r f x = f (x/r) shift :: Float -> (Float -> Float) -> (Float -> Float) Fragen: shift dx f x = f (x - dx) • Wie wirken sich shift, mirror und stretch aus? ... • Wie lassen sie sich als Funktionskomposition darstellen? c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 113 Funktionskomposition c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 114 Funktionskomposition: Variante Vorwärtskomposition Verknüpfung von Funktionen als Kontrollstruktur: f . g bedeutet: wende zuerst g, dann f an; m.a.W. Verknüpfung muss von rechts nach links gelesen werden als ‘g, dann f’ Ausgabe einer Funktion wird Eingabe der nachfolgenden Definition: Definition eines Operators >.>, der Funktionen in der Reihenfolge von links nach rechts verknüpft: (f . g) x = f ( g x ) Typ von ‘.‘: infixl 9 >.> (.) :: (b -> c) -> (a -> b) -> (a -> c) (>.>) :: (a -> b) -> (b -> c) -> (a -> c) Funktionskomposition ist assoziativ, d.h. für alle f, g und h g >.> f = f . g f . (g . h) = (f . g) . h z.B. bei Pictures.hs: rotate = flipH >.> flipV in Haskell aus technischen Gründen als rechtsassoziativ behandelt c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 115 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 116 Funktionskomposition: eine Funktion zweimal anwenden Funktionskomposition Verallgemeinerung: n-fach wiederholte Funktionsanwendung twice :: (a -> a) -> (a -> a) twice f = f . f iter :: Int -> (a -> a) -> (a -> a) iter n f | n > 0 = f . iter (n-1) f |otherwise = id Sei succ :: Int -> Int succ n = n + 1 Frage: Was ergibt iter n double 1 ==> ... ? Was ergibt dann (twice succ) 7 ==> ... ? [Bem.: Variante: Mit Faltung definierbar als iter n f = foldr (.) id (replicate n f) ] c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 117 Partielle Anwendung c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 118 Partielle Anwendung cont. Beispiel: zwei partielle Anwendungen: multiply :: Int -> Int -> Int multiply x y = x * y • multiply 2 :: Int -> Int • map (multiply 2) :: [Int] -> [Int] Frage: was ergibt multiply 2 ==> ... ? Variante ohne partielle Anwendung von map: doubleAll xs = map (multiply 2) xs Beispiel: doubleAll :: [Int] -> [Int] doubleAll = map (multiply 2) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 119 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 120 Bestimmung des Typs einer partiellen Anwendung Assoziativität Streichungsregel (cancellation rule): Wenn der Typ einer Funktion f t1 -> t2 -> ...tn -> t ist und diese wird angewendet auf e1::t1 , e2::t2 , ..., ek ::tk mit (k<=n), Beachte: dann ergibt sich der Ergebnistyp durch „Streichen“ der Typen t1 bis tk , • -> ist rechtsassoziativ, d.h. a -> b -> c entspricht a -> (b -> c) d.h. der Ergebnistyp von f e1 e2 ...ek ist • -> ist nicht assoziativ; Beispiel: • Funktionsanwendung ist linksassoziativ, d.h. f x y = (f x) y f x y /= f (x y) tk+1 -> tk+2 -> ...tn -> t c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), g :: (Int -> Int) -> Int g h = (h 0) + (h 1) 121 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), Funktionen in Haskell Funktionen in Haskell Beachte: jede Funktion in Haskell nimmt exakt ein Argument Allgemein: f e1 e2 ...ek bzw. t1 -> t2 -> ...tn -> t So bedeutet multiply :: Int -> Int -> Int wegen der Rechtsassoziativität multiply :: Int -> (Int -> Int) Damit multiply 2 :: Int -> Int und (multiply 2) 5 :: Int c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 122 sind Abkürzungen für (...((f e1) e2 ) ...ek ) bzw. t1 -> (t2 -> (...(tn -> t) ...)) 123 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 124 Schlussfolgern über Programme: Schlussfolgern über Programme Beweise auf Funktionsebene (vgl. [S.Thompson, 1999, 10.9]) • Prinzip der Extensionalität: zwei Funktionen f und g sind gleich, wenn sie für jedes Argument jeweils den selben Wert haben Beispiel: f . id = f (compId) • Kontrast: Prinzip der Intensionalität: zwei Funktionen f und g sind nur gleich, wenn sie die selben Definitionen haben Beweis: für bel. Argument x gilt (f . id) x = f (id x) = f x (Def. . ) (Def. id) m.a.W. von ihrer Wirkung her sind die Funktionen f . id und f für alle x identisch c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 125 Schlussfolgern über Programme: Verküpfung zwischen map und Funktionskomposition c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 126 Beispiel cont. • Induktionsbasis: Beweise, dass map (f.g) [] = (map f . map g) [] • Behauptung: für alle endlichen Listen xs gilt: (base) • Induktionsschritt: Beweise, dass map (f.g) xs = (map f . map g) xs (map.3) map (f.g) (x:xs) = (map f . map g) (x:xs) unter der Induktionshypothese Beweis: strukturelle Induktion map (f.g) xs = (map f . map g) xs • verfügbare Gleichungen: (hyp) • Durchführung des Beweises: map f [] = [] map f (x:xs) = f x : map f xs (map.1) (map.2) (f.g) x (comp.1) = f (g x) (ind) ................................................................ ................................................................ ................................................................ • ... c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 127 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 128 weiteres Beispiel: Programmtransformation Beweistechnik: Gegenbeispiel • Behauptung: filter p . map f = map f . filter (p.f) • in [S.Thompson, 1999, p. 162] findet sich – ohne Bedingungen an die Funktion f zu formulieren – die folgende Behauptung: . . . We can also define foldr1 from foldr, thus • Beispiel für Anwendung: filter (0<) . map (+1) = map (+1) . filter ((0<).(+1)) = map (+1) . filter ((0<=)) foldr1 f (x:xs) = foldr f x xs • Diese Behauptung erscheint zunächst plausibel, denn es gilt u.a.: Prelude> 6 Prelude> 6 Prelude> 120 Prelude> 120 Beweis durch strukturelle Induktion: • Erinnerung: filter p [] = [] filter p (x:xs) | p x = x:filter p xs | otherwise = filter p xs (filter.1) (filter.2) (filter.3) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 129 Beweistechnik: Gegenbeispiel foldr1 (+) [1,2,3] foldr (+) 1 [2,3] foldr1 (*) [1 .. 5] foldr (*) 1 [2 .. 5] c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 130 Beispiel: foldr1 und foldr Gegenbeispiele machen deutlich, dass (foldr1.0) zumindest nicht für alle (beliebigen) Funktionen f gilt: Prelude> "abcd" Prelude> "bcda" Prelude> 8 Prelude> 9 (foldr1.0) • Behauptung: für alle endlichen Listen xs und assoziative und kommutative Funktionen f gilt: foldr1 f (x:xs) foldr1 (++) ["a","b","c","d"] = foldr f x xs • Struktur des Beweises: • Induktionsbasis: Beweise, dass foldr (++) "a" ["b","c","d"] .................................................... foldr1 (^) [2,3] foldr • Induktionsschritt: Beweise, dass (^) 2 [3] .................................................... unter der Induktionshypothese .................................................... Frage: Welche Forderungen muss f erfüllen, damit (foldr1.0) gilt? c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 131 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 132 Einführung in Prolog: Einführung in Prolog: Prolog . . . Programming in Logic (s.a. [M.L.Scott, 2000, 11.3]) • Axiome werden in Standard-Notation, als sog. Horn-Klauseln geschrieben • Programmierer kann eine Kollektion von Axiomen angeben, aus denen Theoreme abgeleitet werden können • Benutzer gibt Ziel (goal), d.h. zu beweisendes Theorem, vor und die Implementierung versucht, mit Axiomen und Inferenzschritten das Ziel zu beweisen • zu Inferenzschritten gehört auch Auswahl von Werten für Variable • Horn-Klauseln bestehen aus – Kopf (head) oder Konsequenz H – Körper (body) aus Termen Bi H ← B1, B2, ..., Bn – Semantik: wenn alle Bi wahr, dann H ebenfalls wahr – lies: H, wenn B1, B2, ... und Bn • Beachte: nicht alle logischen Aussagen lassen sich als Hornklauseln schreiben c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 133 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), Einführung in Prolog: 134 Einführung in Prolog: • Kern der Verarbeitung ist Schlussfolgern mit Resolution • bei der Resolution können freie Variable durch Unifikation mit Ausdrücken in übereinstimmenden Termen Werte bekommen • Beispiel: • Beispiel: C ← A, B D ←C mortal(X) ← human(X) human(sokrates) D ← A, B mortal(sokrates) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 135 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 136 Einführung in Prolog: Einführung in Prolog: Elementare Syntax Analogie: • Klauseln aus Termen zusammengesetzt • Interpreter funktionaler Sprachen bewertet Funktionen in einer Umgebung mit anderen definierten Funktionen und Konstanten • Prolog: Interpreter arbeitet im Kontext einer Datenbasis von Klauseln (Horn-Klauseln), die als wahr gelten c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 137 Einführung in Prolog: Elementare Syntax • Konstante ist entweder Atom oder Zahl • Struktur ist entweder ein logisches Prädikat oder eine Datenstruktur c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 138 Syntax: Variable • Atome . . . ähneln Symbolen in Lisp • lexikalisch: Identifikator beginnend mit Grossbuchstaben z.B. Kopf Kontostand X Y3 • lexikalische Konventionen: – wenn Identifikator, dann beginnend mit Kleinbuchstaben; z.B. hans uni magdeburg – Zeichenketten in Anführungszeichen (quotes): z.B. ’Hallo, Welt’ – spezielle Zeichen:+ * • Skopus einer Variable ist immer die Klausel, in der sie auftaucht • keine Typendeklaration; Typüberprüfung nur zur Laufzeit, wenn spezielle Verwendung versucht wird (vgl. Lisp) • Variable können durch Unifikation instantiiert werden (d.h. einen Wert annehmen) • Zahlen . . . wie in anderen Programmiersprachen c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), • Terme können Konstante, Variable oder Strukturen sein 139 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 140 Fakten vs. Regeln Syntax: Strukturen • aus Funktor und Liste von Argumenten, die bel. Terme, d.h. Atome oder Variable oder Strukturen, sind • Klauseln lassen sich unterscheiden in Fakten und Regeln • Fakten . . . Horn-Klauseln ohne rechte Seite • Beispiele: happy(doris). married(gerhard, doris) bundeskanzler(gerhard) bintree(a, bintree(b,c)) married(doris, gerhard). bundeskanzler(gerhard). • Beachte: ’(’ unmittelbar, ohne Leerzeichen, an Funktor anschliessend (rechte Seite ist ’leer’, d.h. linke Seite gilt ohne Voraussetzungen) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 141 Fakten vs. Regeln c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 142 Listen in Prolog • Regeln . . . Horn-Klauseln mit rechter Seite aus konjunktiv verknüpften Termen • da Listen oft verwendet werden, gibt es dafür bequeme Darstellungen • Beispiel: Liste mit (vier festen) Personennamen happy(Woman) :- married(Woman, Man), vip(Man). [max, paula, susi, tom] vip(Person) :- bundeskanzler(Person). • Listen können auch Variable enthalten für Elemente, die noch bestimmt werden müssen vip(Person) :- president(Person). – Beispiel: Liste mit vier noch unbekannten Personennamen [P1, P2, P3, P4] – Beispiel: Liste mit (zwei) bekannten und (zwei) noch offenen Namen [max, FreundinVonMax, susi, SusisFreund] c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 143 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 144 Listen in Prolog Listen in Prolog • eine spezielle Notation erlaubt, Listen mit variablen Listenresten darzustellen • die Notation für variable Listenresten erlaubt allgemeinere Darstellungen – Syntax: [Kopf | Rest] – Semantik: die dargestellte Struktur ist unifizierbar mit einer Liste mit einem bel. ersten Element und einer (evtl. leeren) Restliste bel. Länge – Beispiele: [max, paula, susi, tom] Kopf = max Rest = [paula, susi, tom] [max] Kopf = max Rest = [] c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 145 Listen in Prolog – Syntax: [Elem1, Elem2, ... , ElemN | Rest] – Semantik: die dargestellte Struktur ist unifizierbar mit einer Liste mit N Elementen, die jeweils mit den Variablen oder Konstanten Elem1, Elem2, ... , ElemN unifizieren, und einer (evtl. leeren) Restliste bel. Länge – Beispiele: ∗ eine Liste mit mind. drei bekannten und evtl. weiteren Elementen [max, paula, susi | Weitere] ∗ eine Liste mit mind. vier und evtl. weiteren Elementen [Elem1, Elem2, Elem3, Elem4 | Rest] c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 146 Listen in Prolog: Relation append • Programmieren in Prolog erfordert ‘Denken in Relationen’ • Fakt: falls List1 leer, dann liegt Relation append für beliebige Listen List2 vor mit List3 unifiziert mit List2 • Beispiel: Aneinanderhängen von Listen append([], List, List). • statt wie in funktionalen Sprachen eine (zweistellige) Funktion zum Aneinanderhängen von Listen zu entwerfen, fragt man sich beim ‘Denken in Relationen’: Wann liegt zwischen drei beliebigen endlichen Listen List1, List2 und List3 die (dreistellige) Relation append vor? • die Relation append soll dabei genau dann zwischen List1, List2 und List3 vorliegen, wenn List3 sich durch Aneinanderhängen von List1 und List2 ergibt c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 147 • Regel: falls List1 nicht leer (d.h. setzt sich aus Kopf H und bel. Rest R1 zusammen), dann liegt Relation append zwischen List1(= [H1|R1]), einer beliebigen Liste List2 und einer aus H1 als Kopf und Z als Rest gebildeten Liste vor, wenn append zwischen R1, List2 und Z vorliegt append([H1|R1], List2, [H1|Z]) :- append(R1, List2, Z). c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 148 Prolog: Generieren mehrfacher Lösungen Prolog: Generieren mehrfacher Lösungen cont. (vgl. Clocksin u. Mellish, Ch. 4.1) • wenn Prolog Lösungen auf unterschiedlichem Wege bestimmt, gelten sie als unterschiedlich • Reihenfolge bei der Abarbeitung der Teilziele der rechten Seite einer Klausel: von links nach rechts ?- possible_pair(X,Y). X = john, Y = susi ; X = john, Y = mary ; ... • Beispiel possible_pair(X,Y) :- boy(X),girl(Y). c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 149 Generieren mehrfacher Lösungen cont. c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 150 Generieren mehrfacher Lösungen cont. • unendlich viele Lösungen • alternative Lösungen is_integer(0). • z.B. bei Zielen mit vielen uninstantiierten Variablen is_integer(X) :- is_integer(Y), X is Y+1. member(X, [X|_]). member(X, [_|Y]) :- member(X,Y). • Einschub: Prädikat is – wird in Infix-Notation verwendet: Var is Term – zum Zeitpunkt der Auswertung müssen Variable auf der rechten Seite so instantiiert sein, dass der Term auf der rechten Seite ausgewertet werden kann – die Variable auf der linken Seite wird mit dem errechneten Wert zu unifizieren versucht c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), possible_pair(X,Y) :- boy(X),girl(Y). boy(john). boy(paul). ... girl(susi). girl(mary). ... 151 ?- member(a, X). ... ?- member(a, [a,b,r,a,c,a,d,a,b,r,a]). /* 5 mal erfolgreich */ c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 152 Verarbeitung eines Ziels in einer Prolog-Datenbasis (skizziert) Der ‘Cut’ (vgl. Clocksin u. Mellish, Ch. 4.2) • von oben nach unten • ein Ziel kann entweder mit einem Fakt oder mit dem Kopf einer Regel unifiziert werden; dabei werden ggf. Variablen instantiiert • diese Stelle wird in Datenbasis markiert • bei einer Regel müssen dann die Teilziele der rechten Seite erfüllbar sein • falls dies fehlschlägt (oder wenn weitere Lösungen verlangt) kann eine alternative Klausel für das Ziel gesucht werden (Backtracking) • dazu: die Instantiierungen aus der letzten Unifikation mit dem Ziel werden aufgehoben und die Suche geht an der markierten Stelle weiter c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 153 • spezieller Mechanismus, um in Prolog-Programmen auszudrücken, welche Möglichkeiten beim Backtracking nicht mehr betrachtet werden müssen • syntaktisch: ! d.h. Ziel mit Prädikat ! (sprich: Cut) ohne Argumente • semantisch: wenn ein sog. Cut angetroffen wird, dann ist das System danach festgelegt auf alle Auswahlen, die getroffen wurden, seit das Elternziel (d.h. der Kopf der aktuell bearbeiteten Regel) invoziert wurde. Alle Alternativen für das Elternziel und die Teilziele bis zum Cut werden entfernt und nicht weiterverfolgt. c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), Typische Verwendungen des ‘Cut’ Beispiel: Bestätigen einer Regelauswahl (vgl. Clocksin u. Mellish, Ch. 4.3) • • Bestätigen einer Regelauswahl Wenn Du soweit gekommen bist, hast Du die richtige Regel für das Ziel ausgewählt. • Fehlschlagenlassen eines Ziels ohne Suche nach alternativen Lösungen Wenn Du soweit gekommen bist, solltest Du aufhören, dieses Ziel zu verfolgen. • Beenden des Erzeugens alternativer Lösungen durch Backtracking Wenn Du soweit gekommen bist, hast Du die Lösung bzw. alle Lösungen gefunden, und es gibt keinen Grund, nach weiteren Alternativen zu suchen. c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 154 155 sum_to(N,X) ... wenn N ganze Zahl, dann X die Summe der Zahlen von 1 bis N sum_to(1,1) :- !. sum_to(N,R) :- N1 is N-1, sum_to(N1,R1), R is R1+N. • für N mit 1 instantiiert würde auch die zweite Klausel zutreffen • Cut drückt in erster Klausel aus, dass dies die einzige anzuwendende Regel ist für den Fall N mit 1 instantiiert c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 156 Beispiel: Bestätigen einer Regelauswahl Die Kombination ‘cut-fail’ • • Variante: sum_to(N,1) :- N=<1, !. • wird vor ein fail ein Cut eingebaut (sog. ‘cut-fail’-Kombination), so wird Backtracking verhindert und keine Alternative gesucht sum_to(N,R) :- N1 is N-1, sum_to(N1,R1), R is R1+N. • hier wird der Fehler unendlicher Berechnung für N gleich oder kleiner Null vermieden c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), fail ... eingebautes nullstelliges Prädikat: schlägt fehl und verursacht Backtracking 157 Cut zum Beenden eines ‘Generiere und Teste’ • Beispiel: average_taxpayer(X) :- foreigner(X),!,fail. average_taxpayer(X) :- spouse(X,Y), gross_income(Y,Inc), Inc > 3000,!,fail. average_taxpayer(X) :- ... c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 158 Die Kombination ‘cut-fail’ • Beispiel: ganzzahlige Divison könnte wie folgt implementiert werden • mögliche Darstellung von not: divide(N1, N2, Result) :is_integer(Result), Product1 is Result*N2, Product2 is (Result+1)*N2, Product1 =< N1, Product2 > N1, !. not(P) :- call(P),!, fail. not(P). • da das Ergebnis eindeutig ist, kann mit dem Cut der vergebliche Versuch verhindert werden, weitere Lösungen zu finden c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 159 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 160 Prolog: Entwickeln von Programmen Prolog: Entwickeln von Programmen • Beispiel: eine Relation rev soll entwickelt werden, die zwischen zwei Listen genau dann vorliegt, wenn die eine Liste genau die Elemente der anderen Liste enthält, aber in umgedrehter Reihenfolge • m.a.W. rev soll sich wie folgt verhalten: 9 ?- rev([1,b,c],X). • erster Schritt: Aufstellen einiger elementarer Fälle, also hier z.B. rev([],[]). rev([X],[X]). rev([X,Y],[Y,X]). rev([X,Y,Z],[Z,Y,X]). ... X = [c, b, 1] Yes c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 161 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 162 Prolog: Entwickeln von Programmen Prolog: Entwickeln von Programmen • zweiter Schritt: Regel für den allgemeinen Fall ableiten, d.h. für eine nichtleere Liste aus Kopf und Rest • Es gilt: [Kopf] wird angehängt an die Liste RevRest, die mit Rest in der Relation rev steht, also: rev([Kopf|Rest],Res) :- ... rev(Rest, RevRest), append(RevRest, [Kopf], Res) • Was ist über Resultat ( Res) bekannt? • damit Regel komplett: append(...,[Kopf],Res) rev([Kopf|Rest],Res) :- rev(Rest,RevRest), append(RevRest,[Ko • An welche Liste wird die einelementige Liste [Kopf] angehängt? • Programm komplett zusammen mit Fakt für leere Liste: rev([],[]). c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 163 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 164 Prolog: Such- und Ausführungsordnung Prolog: Abarbeitung von Zielen s.a. [M.L.Scott, 2000, 11.3] Für logische Ableitungen sind prinzipiell zwei Suchstrategien möglich: • Der Suchraum bei der Strategie der Rückwärtsverkettung lässt sich darstellen als ein sog. AND-OR-Baum • Gehe von existierenden Klauseln (den ’Axiomen’) aus und versuche, daraus durch wiederholte Resolutionsschritte das Ziel abzuleiten (sog. Vorwärtsverkettung oder forward chaining) • Gehe von Ziel aus und versuche es rückwärts so ’aufzulösen’, dass existierende Klauseln erreicht werden (sog. Rückwärtsverkettung oder backward chaining) • Prolog verwendet backward chaining c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 165 • Ein AND-OR-Baum besteht aus sich abwechselnden AND- und OR-Ebenen • Auf einer AND-Ebene liegen die konjunktiv verknüpften Teilziele der rechten Seite einer Regel • Auf der darunterliegenden OR-Ebene liegen die alternativen Möglichkeiten (disjunktiv verknüpft), ein Teilziel der darüberliegenden Ebene zu erfüllen, d.h. alle die Hornklauseln aus der Datenbasis, deren linke Seite mit dem darüberliegenden Teilziel unifiziert werden kann. c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 166 Prolog: Abarbeitung von Zielen Prolog: Abarbeitung von Zielen • Der AND-OR-Baum wird vom Prolog-Interpreter in Form einer Tiefensuche (depth first search) durchlaufen. • Aus der Abarbeitungsstrategie ergibt sich, dass linksrekursive Regeln zu Problemen bei der Termination führen können • Die OR-Alternativen werden dabei in dem Sinne nacheinander von links nach rechts verarbeitet, dass zunächst mit einer Alternative und der daraus resultierenden Instantiierung von Variablen so lange weitergearbeitet wird, bis das Ursprungsziel erfüllt ist oder bis ein Teilziel fehlschlägt. • Im letzteren Fall wird zur letzten nicht behandelten OR-Alternative zurückgegangen und – nachdem die seit diesem Punkt erfolgten Instantiierungen aufgehoben wurden – dort mit der Suche fortgefahren (sog. backtracking) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 167 • Beispiel: ein gerichteter, azyklischer Graph (vgl. [M.L.Scott, 2000, 11.3]) edge(a, b). edge(d, e). edge(b, c). edge(b, e). edge(c, d). edge(d, f). • mögliche Regel für Pfade in diesem Graph: path(X,X). path(X,Y) :- edge(Z,Y),path(X,Z). • Was passiert bei folgender Anórdnung? path(X,Y) :- path(X,Z), edge(Z,Y). path(X,X). c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 168 Meta-Programmierung in Prolog Meta-Programmierung in Prolog Klauseln als Terme (vgl. Clocksin u. Mellish, Ch. 6.4) Zusammenhang zwischen Prolog-Klauseln und Strukturen • Klauseln können als gewöhnliche Prolog-Strukturen betrachtet und behandelt werden • Fakt: Struktur ist Prädikat mit den Argumenten likes(john,X). • Möglichkeiten: • Regel: Struktur mit Hauptfunktor ’:-’ (als Infix-Operator) und zwei Argumenten – Struktur konstruieren, die Klausel in Datenbasis darstellt – eine Klausel zur Datenbasis hinzufügen – eine Klausel aus Datenbasis entfernen c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), – erstes Argument: Kopf – zweites Argument: Körper – m.a.W.: Regel likes(john,X) :- likes(X,wine) entspricht Struktur ’:-’(likes(john,X),likes(X,wine)) 169 Meta-Programmierung in Prolog c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 170 (Meta-)Prädikate zum Untersuchen und Ändern von Klauseln Zusammenhang zwischen Prolog-Klauseln und Strukturen cont. • mehrere Ziele im Körper werden als durch den zweiargumentigen InfixOperator ’,’ verknüpft betrachtet • clause(X,Y) . . . kann erfüllt werden, wenn sich X und Y mit Kopf bzw. Körper einer in der Datenbasis existierenden Klausel unifizieren lassen • Beispiel: • X muss dabei soweit instantiiert sein, dass das Hauptprädikat der Klausel feststeht grandparent(X,Z) :- parent(X,Y),parent(Y,Z) • kann auch für Fakten (mit dem Körper true) angewendet werden entspricht ’:-’(grandparent(X,Z), ’,’(parent(X,Y),parent(Y,Z))) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 171 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 172 (Meta-)Prädikat clause (Meta-)Prädikate cont. • Beispiel: • asserta(X) . . . Hinzufügen der Klausel X an Anfang der DB • assertz(X) . . . Hinzufügen der Klausel X an Ende der DB • X muss dabei soweit instantiiert sein, dass das Hauptprädikat feststeht ?- clause(append(A,B,C),Y). • der Effekt des Hinzufügens wird beim Backtracking nicht aufgehoben, sondern nur durch explicites retract(X) • retract(X) . . . die erste Klausel, die mit X unifizierbar, wird aus der DB entfernt c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 173 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), (Meta-)Prädikate für Strukturen: Konstruktion und Zugriff (vgl. Clocksin u. Mellish, Ch. 6.5) 174 (Meta-)Prädikate . . . Strukturen cont. • arg(N,T,A) • functor(T,F,N) . . . T ist eine Struktur mit Funktor F und Arität N • erstes und zweites Argument müssen instantiiert sein • Verwendungen: – T instantiiert (zumindest Hauptprädikat): Funktor wird extrahiert und Arität bestimmt – T uninstantiiert, aber F und N instantiiert: Ergebnis ist konstruierte Struktur • arg(N,T,A) erfolgreich, wenn N-tes Argument von Struktur T mit A unifizierbar • Beispiele: • Beispiel: copy(Old,New) :- functor(Old,F,N),functor(New,F,N). c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 175 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 176 (Meta-)Prädikate . . . Strukturen cont. Operatoren zur Verknüpfung von Zielen • X =.. L (sprich: univ) . . . L ist die Liste, die aus dem Funktor von X gefolgt von den Argumenten von X besteht; • zwei Verwendungsformen: – X instantiiert, Liste wird konstruiert und mit L zu unifizieren versucht – X uninstantiiert, aber L mit Atom als erstem Element instantiiert; Struktur X wird konstruiert (vgl. Clocksin u. Mellish, Ch. 6.7) Konjunktion: • ’,’ als vordefinierter rechtsassoziativer Infixoperator, d.h. X,Y,Z entspricht X,(Y,Z) • X,Y . . . erfolgreich, wenn X und Y erfolgreich; wenn Y fehlschlägt, wird X erneut zu erfüllen versucht (Backtracking); wenn X fehlschlägt, schlägt die Konjunktion fehl • Beispiele: c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 177 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), Verknüpfung von Zielen cont. 178 Verknüpfung von Zielen cont. Disjunktion: Disjunktion: cont. • ’;’ als vordefinierter rechtsassoziativer Infixoperator, d.h. person(X) :- (X=adam; X=eve; mother(X,Y)). entspricht person(X) :- ’;’(X=adam, ’;’(X=eve, mother(X,Y))) • Semantik von ’;’: – X;Y ist erfolgreich, wenn X erfolgreich oder Y erfolgreich; – wenn X fehlschlägt, wird versucht, Y zu erfüllen; – wenn Y fehlschlägt, schlägt die Disjunktion fehl • alternative Darstellung: person(adam). person(eve). person(X) :- mother(X,Y). c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 179 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 180 (Meta-)Prädikate für Ziele (Meta-)Prädikate für Ziele cont. (vgl. Clocksin u. Mellish, Ch. 6.7) • call(X) . . . X muss als Term so instantiiert sein, dass dieser als Ziel interpretierbar • call(X) ist erfolgreich, wenn X erfüllt werden kann, sonst Fehlschlag • not(X) ist erfolgreich, wenn X fehlschlägt, not(X) schlägt fehl, wenn X erfolgreich • negation as failure • wichtig für Ziele, die erst zur Laufzeit konstruiert werden ..., Z=..[P,X,Y], call(Z), ... (dabei seien P als Funktor und X und Y als geeignete Argumente instantiiert) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), • not(X) . . . X muss als Term so instantiiert sein, dass dieser als Ziel interpretierbar 181 Beachte: Unterschied zwischen ?- member(X,[a,b,c]), write(X). und ?- not(not(member(X,[a,b,c]))), write(X). c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 182 Beziehung zwischen not und ! Beziehung zwischen not und ! • Cut zur Bestätigung der Regelauswahl durch Verwendungen von not ersetzbar • Cut durch not zu ersetzen, macht i.A. Programme lesbarer, aber ggf. aufwendiger – Beispiel: A :- B,!,C. A :- D. – ersetzbar durch A :- B,C. A :- not(B),D. – für sum_to: sum_to(1,1). sum_to(N,R) :- not(N=1), N1 is N-1, sum_to(N1,R1), R is R1+N. – bzw. sum_to(N,1) :- N=<1. sum_to(N,R) :- not(N=<1), N1 is N-1, sum_to(N1,R1), R is R1+N. • mögliche Darstellung von not: not(P) :- call(P),!, fail. not(P). c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 183 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 184 Anwendung von asserta und retract Anwendung von asserta und retract Beispiel: Erzeugung von Zufallszahlen (vgl. Clocksin u. Mellish, Ch. 7.8.1) • Verwendung: • random(R,N) . . . instantiiert N mit einer zufälligen ganzen Zahl zwischen 1 und R • mögliche Definition: :- dynamic seed/1. seed(13). random(R,N) :- retract(seed(S)), N is (S mod R) + 1, NewSeed is (125*S+1) mod 4096, asserta(seed(NewSeed)),!. c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), • durch repeat kann wiederholtes Erzeugen einer Zufallszahl (bis eine Bedingung erfüllt) erreicht werden; z.B.: ?- repeat, random(10,X), write(X), nl, X=7. 185 Anwendung von asserta und retract c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 186 Meta-Programmierung in Prolog: Map: Prädikat auf alle Listenelemente anwenden (vgl. Clocksin u. Mellish, Ch. 7.12) • Variante: ... seed(13). random(R,N) :- seed(S), N is (S mod R) + 1, retract(seed(S)), NewSeed is (125*S+1) mod 4096, asserta(seed(NewSeed)),!. • maplist(P,L,M) . . . erfolgreich durch Anwenden des zweiargumentigen Prädikats P auf jedes einzelne Element der Liste L und ‘Kreieren’ einer neuen Liste M mit den bei Anwenden des Prädikats gewonnenen Ergebnissen • mögliche Definition: • aufgrund der Semantik von retract (Unifikationen bleiben erhalten) ist die obige, einfachere Lösung gleichwertig c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), ?- random(10,X) X = 4 ; No ?- listing(seed). :- dynamic seed/1. seed(1626). Yes 187 maplist(_,[],[]). maplist(P,[X|L],[Y|M]) :Q =.. [P,X,Y], call(Q), maplist(P,L,M). c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 188 maplist Meta-Programmierung in Prolog: Checklist: Prüfen, ob Prädikat auf alle Listenelemente zutrifft • Beispiele: ?- maplist(fak, [1,2,3,4,5], Z). • checklist(P,L) . . . erfolgreich, wenn einargumentiges Prädikats P auf jedes einzelne Element der Liste L zutrifft ?- maplist(plus(2), [1,2,3,4,5], Z). • mögliche Definition: checklist(_,[]). checklist(P,[X|L]) :Q =.. [P,X], call(Q), checklist(P,L). c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 189 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), checklist: 190 Meta-Programmierung in Prolog: Findall: Finden aller Lösungen (vgl. Clocksin u. Mellish, Ch. 7.8.3) • Beispiele: • findall(X,G,L) . . . ‘konstruiert’ eine Liste aus all den Objekten X, für die das Ziel G erfüllt und unifiziert diese mit der Liste L dabei: G ist als Term instantiiert, der als Ziel interpretierbar und der Variable X irgendwo enthält ?- checklist(var, [X,Y,Z]). ?- checklist(var, [1,atom | X]). • Beispiel: ?- findall(X,append(_,[X,X|_],[1,2,2,2,3,1,3,3]),B). c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 191 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 192 findall: bagof und setof: • entspricht bagof/3 mit allen freien Variablen mit dem Existenzquantor ^ gebunden mit dem einzigen Unterschied, dass bagof/3 fehlschlägt, wenn das Ziel keine Lösung hat • bagof(X,G,L) . . . bestimmt eine Liste von Werten für X, für die das Ziel G erfüllt, und unifiziert diese mit der Liste L • wenn Ziel G freie Variable enthält, wird für jede Belegung dieser Variable die Bestimmung der Werte für L getrennt durchgeführt • mit dem Existenzquantor ^ kann angegeben werden, welche Variablen als gebunden betrachtet werden sollen c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 193 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), bagof: setof: Beispiele: • setof(X,G,L) . . . analog zu bagof(X,G,L); nur ist L sortiert und enthält keine mehrfachen Vorkommen ?- listing(foo). foo(a, foo(a, foo(b, foo(b, foo(c, b, b, c, c, c, 194 • Beispiel: c). d). e). f). g). ?- setof(X,append(_,[X,X|_],[1,2,2,2,3,1,3,3]),B). ?- bagof(C, foo(A, B, C), Cs). ?- bagof(C, A^foo(A, B, C), Cs). c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 195 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 196 Funktionale vs. logische Programmierung: Funktionale vs. logische Programmierung: Vergleich theoretische Basis der Berechnungen • funktionale Programme unterscheiden zwischen Eingaben (Argumente von Funktionen) und Ausgaben (Werte von Funktionen) • funktionale Programme: Substitution • relationale Programme in Prolog: Unifikation und Resolution • relationale Programme treffen diese Unterscheidung nicht notwendigerweise • was Eingabe und was Ausgabe einer Berechnung mit einem relationalen Programm sein soll, wird durch die Art der Invokation festgelegt (m.a.W. höhere Flexibilität) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 197 Funktionale vs. logische Programmierung: 198 Fallbeispiel: Umdrehen einer Liste cont. Fallbeispiel: Umdrehen einer Liste • Haskell: mit Pattern matching • Prolog: rev :: [a] -> [a] rev([],[]). rev([X|Y],Z) :- rev(Y,Z1), append(Z1,[X],Z). rev [] = [] rev (x:xs) = rev xs ++ [x] • Haskell: Variante mit Fallunterscheidung • Scheme: rev :: Eq a => [a] -> [a] (define (rev l) (if (null? l) l (append (rev (cdr l)) (list (car l))))) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), rev x = if x == [] then [] else rev (tail x) ++ [head x] 199 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 200 Funktionale vs. logische Programmierung: Fallbeispiel: Mergesort cont. Fallbeispiel: Mergesort (cf. [Mueller & Page, Ch. 34]) • merge is predefined, redefined as mymerge • Prolog: mymerge([], X, X). mymerge(X, [], X). mergesort([], []) :- !. mergesort([X], [X]) :- !. mymerge([X1|R1], [X2|R2], [X1|R]) :- precedes(X1, X2), mymerge(R1, [X2|R2], R). mergesort(X, Sorted) :- split(X, X1, X2), mergesort(X1, S1), mergesort(X2, S2), merge(S1, S2, Sorted). mymerge([X1|R1], [X2|R2], [X2|R]) :- precedes(X2, X1), mymerge([X1|R1],R2, R). • Prolog benötigt Hilfsvariable (hier: S1, S2), um Zwischenergebnisse zu ‘transportieren’ c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 201 Fallbeispiel: Mergesort cont. c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 202 Fallbeispiel: Mergesort (cf. [Mueller & Page, Ch. 34]) • weitere Hilfsprädikate: • Haskell: /* split a list of n elements into two near halfes */ sort :: Ord a => [a] -> [a] /* firstn(X, 0, [], X). */ firstn([X|Y], 1, [X], Y). firstn([X|Y], N, [X|YN], RN) :- N1 is N-1, firstn(Y, N1, YN, RN). sort [] = [] sort [x] = [x] sort seq = merge (sort a) (sort b) where (a,b) = split seq split(X, X1, X2) :- length(X,N), N2 is ceiling(N/2), firstn(X, N2, X1, X2). • flexible Kombination von Funktionsaufrufen • in Verbindung mit Pattern zum Zugriff auf Elemente eines Paares c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 203 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 204 Fallbeispiel: Mergesort (cf. [Mueller & Page, Ch. 34]) Wichtiger Unterschied • Hilfsfunktionen: • Pattern matching in Prolog: split :: [a] -> ([a],[a]) split [] = ([],[]) split seq = splitAt (div (length seq) 2) seq all_equal([]). merge :: Ord a => [a] -> [a] -> [a] merge xs [] = xs merge [] ys = ys merge (x:xs) (y:ys) | x == y = x:(merge xs ys) | x < y = x:(merge xs (y:ys)) | otherwise = y:(merge (x:xs) ys) all_equal([X,X | Y]) :- all_equal([X | Y]). c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), all_equal([_]). 205 Wichtiger Unterschied cont. 206 Relationale Programme als ‘Generatoren’ • all_equal als Prädikat • Pattern matching in Haskell: ?- all_equal([a,a,a,a]). Yes allEqual :: Eq a => [a] -> Bool allEqual [] = True allEqual [_] = True allEqual (el1:el2:rest) = if el1 == el2 then allEqual (el2:rest) else False • all_equal als ‘Generator’ • Haskell: eine Variable darf in einem Pattern auf der linken Seite einer Definition nur einmal auftauchen c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 207 ?- all_equal(X). X = [] ; X = [_G205] ; X = [_G205, _G205] ; X = [_G205, _G205, _G205] ; ... c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 208 Scheme als funktionale Sprache: Scheme als funktionale Sprache: • Diskussion von Scheme im Vergleich mit Haskell • Funktionen Objekte erster Ordnung • m.a.W. Gemeinsamkeiten und Unterschiede 1 ]=> (define pi 3.1415) ;Value: pi • Haskell als ‘Blaupause’ für funktionale Sprachen 1 ]=> pi ;Value: 3.1415 Grundlegendes: 1 ]=> (define quadriere (lambda (zahl) (* zahl zahl))) ;Value: quadriere • Interpreter • Definition von Werten mit define 1 ]=> (quadriere pi) ;Value: 9.86902225 (define <name> <wert>) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 209 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), Scheme: Anonyme Funktionen 210 Scheme: Anonyme Funktionen alternative Syntax für benannte Funktionen • Syntax: • (define (<name> <par-1> ... <par-n>) <koerper>) (lambda <parameterliste> <koerper>) • überall dort verwendbar, wo auch mit Symbol auf Funktion verwiesen werden kann 1 ]=> ((lambda (n) (* n n)) 3) • statt (define <name> (lambda (<par-1> ... <par-n>) <koerper>)) ;Value: 9 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 211 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 212 Scheme vs. Haskell Scheme: Übersicht • in Scheme: Klammerung innerhalb geschachtelter Ausdrücke; in Haskell: Vorrangregeln • vordefinierte Datentypen – Zahlen: ganze, rationale, Fliesskomma, ... – Strings – Zeichen – boolesche Werte: #t, #f • in Scheme: keine Typisierung; in Haskell: strenge Typisierung • wichtigster aggregierter Datentyp: Liste – Konstruktor: cons – Selektoren: car, cdr – Prädikate: null? • Kontrollstrukturen: – if – cond • Scheme: strikt; Haskell: non-strikt • in Scheme nicht vordefiniert vorhanden sind u.a. Listenkomprehension und Fallunterscheidung durch Pattern Matching • in Haskell: verzögerte Auswertung (lazy evaluation) für alle Argumente in Scheme: Auswertung für alle Argumente; aber: verzögerte Auswertung und call-by-need möglich mit delay und force • Funktionen zur Konversion zwischen verschiedenen Typen c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 213 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), Striktheit vs. Non-Striktheit Scheme: Programm-Daten-Äquivalenz • Eine (seiteneffekt-freie) Funktion heisst strikt, wenn gefordert wird, dass alle ihre Argumente definiert sind und so die Evaluationsordnung das Ergebnis nicht verändert. • Programme haben die Form von Listen • Eine Funktion heisst non-strikt, wenn für sie die Forderung nach Striktheit nicht erhoben wird. • Programme können mit allen Listenfunktionen bearbeitet werden • Eine Sprache heisst strikt, wenn gefordert wird, dass alle ihre Funktionen strikt. • Eine Sprache heisst non-strikt, wenn sie die Definition non-strikter Funktionen zulässt. c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 214 215 • Scheme (und Lisp) sind homoikonisch, d.h. selbstrepräsentierend • Beispiel: (define compose (lambda (f g) (lambda (x) (f (g x))))) 1 ]=> ((compose car cdr) ’(1 2 3)) ;Value: 2 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 216 Scheme: weitere Aspekte Scheme: Programm-Daten-Äquivalenz • Funktionen mit beliebiger Anzahl von Argumenten möglich (sog. Restparameter, der an Liste gebunden) • Beispiel cont.: (define compose2 (lambda (f g) (eval (list ’lambda ’(x) (list f (list g ’x))) (scheme-report-environment 5)))) • Beispiel: (define (avg . nums) (average nums)) 1 ]=> ((compose2 car cdr) ’(1 2 3)) • average erwartet Liste als Argument ;Value: 2 • Definition: (define (average nums)(/ (apply + nums) (length nums))) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 217 Scheme: Funktionen höherer Ordnung c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 218 Scheme: Funktionen höherer Ordnung • z.B. Falten einer zweistelligen Funktion in eine Liste: • partielle Anwendungen sind bei Funktionen im Curry-Format möglich (define fold (lambda (f l i) (if (null? l) i ;; identity for f (f (car l) (fold f (cdr l) i))))) (define curry (lambda (f) (lambda (a) (lambda (b) (f a b))))) 1 ]=> (((curry +) 3) 4) ;Value: 7 (define curried-plus (curry +)) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 219 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 220 Prozeduren und Prozesse: Prozeduren und Prozesse: Beispiel: Berechnung Fakultät Beispiel: Berechnung Fakultät als linear-rekursiver Prozess Definition: n! = n * (n - 1) * . . . * 2 *1 (factorial 6) (* 6 (factorial 5)) (* 6 (*5 (factorial 4))) (* 6 (*5 (* 4 (factorial 3)))) (* 6 (*5 (* 4 (* 3 (factorial 2))))) (* 6 (*5 (* 4 (* 3 (* 2 (factorial 1)))))) (* 6 (*5 (* 4 (* 3 (* 2 1))))) (* 6 (*5 (* 4 (* 3 2)))) (* 6 (*5 (* 4 6))) (* 6 (*5 24)) (* 6 120) 720 Zusammenhang: n! = n * (n - 1)! (define (factorial n) (if (= n 1) 1 (* n (factorial (- n 1))))) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 221 Beispiel: Berechnung Fakultät als linear-iterativer Prozess c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 222 Beispiel: Berechnung Fakultät als linear-iterativer Prozess in jedem Schritt: (factorial 6) (fact-iter 1 1 6) (fact-iter 1 2 6) (fact-iter 2 3 6) (fact-iter 6 4 6) (fact-iter 24 5 6) (fact-iter 120 6 6) (fact-iter 720 7 6) 720 product <-- counter * product counter <-- counter + 1 (define (factorial n) (fac-iter 1 1 n)) (define (fact-iter product counter max-count) (if (> counter max-count) product (fact-iter (* counter product) (+ counter 1) max-count))) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 223 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 224 Scheme vs. Haskell: Scheme vs. Haskell: Sichtbarkeit von Definitionen Sichtbarkeit von Definitionen Beispiel cont.: • die Definitionen auf dem Toplevel von Scheme sind ‘global’ sichtbar 1 ]=> (define (isEven n) (if (< n 0) () (if (= n 0) #t (isOdd (- n 1))))) • wechselseitige Bezugnahme in rekursiven Definitionen ist möglich ;Value: iseven • Beispiel: 1 ]=> (isEven 5) 1 ]=> (define (isOdd n) (if (<= n 0) () (isEven (- n 1)))) ;Value: () ;Value: isodd ... 1 ]=> (isOdd 5) ;Value: #t c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 225 Lokale Definitionen 226 Scheme: lokale Bindungen mit let und let* Motivation: • Syntax: (let ((var1 val1) (var2 val2) ... (varN valN)) <body>) • Semantik: bei der Auswertung von <body> sind die in var1, var2, ..., varN gebundenen Werte val1, val2, ..., valN verfügbar • Vermeiden wiederholter Berechnungen • Beispiel: addPairwiseRest als Äquivalent zu addPairwise’ • klarer strukturierter Code • Beispiel: eine Funktion addPairwise’, die korrespondierende Elemente zweier Zahlenlisten addiert und – falls eine Liste keine Elemente mehr hat – den aktuellen Rest der anderen an die Liste der Paarsummen anhängt c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 227 (define (addPairwiseRest list1 list2) (let ((front (addPairwise list1 list2)) (minLength (min (length list1)(length list2)))) (let ((rear (append (drop minLength list1) (drop minLength list2)))) (append front rear)))) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 228 Scheme: lokale Bindungen mit let und let* Scheme: lokale Bindungen mit let und let* • Beachte: bei let erfolgt die Auswertung und Bindung in den einzelnen (var1 val1) (var2 val2) ... (varN valN) parallel • let* ist wie let, nur erfolgt Auswertung und Bindung in den einzelnen (var1 val1) (var2 val2) ... (varN valN) sequentiell • daher folgendes falsch: • m.a.W. für die Ausdrücke vali (für 2 <= i <= n) stehen die Bindungen var1, var2, ..., var(i-1) zur Verfügung (define (addPairwiseRest list1 list2) (let ((front (addPairwise list1 list2)) (minLength (min (length list1)(length list2))) (rear (append (drop minLength list1) (drop minLength list2)))) (append front rear))) 1 ]=> (addPairwiseRest ’(4 7 1 1 ) ’(8 15)) ;Unbound variable: minlength c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 229 Scheme: lokale Bindungen mit let und let* c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 230 Beispiel cont. • Hilfsfunktionen a la Haskell: • Beispiel für let*: (define (addPairwiseRest list1 list2) (let* ((front (addPairwise list1 list2)) (minLength (min (length list1)(length list2))) (rear (append (drop minLength list1) (drop minLength list2)))) (append front rear))) (define (addPairwise list1 list2) (if (or (null? list1)(null? list2)) () (cons (+ (car list1)(car list2)) (addPairwise (cdr list1)(cdr list2))))) (define (take n list) (if (= n 0) () (cons (car list) (take (- n 1) (cdr list))))) 1 ]=> (addPairwiseRest ’(4 7 1 1 ) ’(8 15)) (define (drop n list) (if (= n 0) list (drop (- n 1) (cdr list)))) ;Value 12: (12 22 1 1) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 231 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 232 Blockstruktur: Scheme: Berechnung Quadratwurzel mit Newton-Verfahren (define (sqrt x) (define (good-enough? guess x) (< (abs (- (square guess) x)) .001)) (define (improve guess x) (average guess (/ x guess))) (define (sqrt-iter guess x) (if (good-enough? guess x) guess (sqrt-iter (improve guess x) x))) (define (sqrt x) (sqrt-iter 1 x)) (define (sqrt-iter guess x) (if (good-enough? guess x) guess (sqrt-iter (improve guess x) x))) (define (good-enough? guess x) (< (abs (- (square guess) x)) .001)) (sqrt-iter 1 x) ) (define (improve guess x) (average guess (/ x guess))) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 233 Interne Definitionen c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 234 lexikalischer Skopus: Ausnutzen, daß x in sqrt gebunden (lexikalischer Skopus): • freie Variable in einer Prozedur verweisen auf Variable in umfassenden Prozeduren; (define (sqrt x) (define (good-enough? guess) (< (abs (- (square guess) x)) .001)) (define (improve guess) (average guess (/ x guess))) (define (sqrt-iter guess) (if (good-enough? guess) guess (sqrt-iter (improve guess)))) • m.a.W.: Werte freier Variable werden in der Umgebung gesucht, in der die Prozedur definiert wurde (sqrt-iter 1)) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 235 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 236 Ströme: Motivation Ströme: Sprachmittel in SCHEME • Berechnungen auf Kollektionen von Daten • Konstruktor: cons-stream • abstrakt gesehen: Sequenz von Datenobjekten • Selektoren: head, tail • mögl. Implementation: durch Listen • Beziehung: für bel. a,b und x: Wenn x ist (cons-stream a b), dann (head x) ist a und (tail x) ist b. • bessere Implementation: mit verzögerter Auswertung Ströme repräsentieren den zeitlichen Verlauf der durch sie modellierten Systeme c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 237 Ströme: Implementation • Konstante: the-empty-stream • Prädikat: empty-stream? c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 238 Implementation cont. • Implementation von Strömen durch verzögerte Evaluation (delayed evaluation, lazy evaluation) • in normalen Listen: car und cdr werden zur Konstruktionszeit evaluiert • in Strömen: tail erst zur Zugriffszeit evaluieren (call-by-need) • (cons-stream <a><b>) . . . Spezialform, die gleichwertig mit (cons <a> (delay <b>)) – d.h. Strom wird als Paar dargestellt – im cdr steht promise zur Berechnung des tail • (define (head stream) (car stream)) • Spezialform delay: (delay <exp>) . . . liefert ein verzögertes Objekt, sog. promise, dass erst mit force zur Ausführung veranlasst wird • (define (tail stream) (force (cdr stream))) • (force <promise>) . . . veranlasst Ausführung von <promise> c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 239 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 240 Unendlich lange Ströme Unendlich lange Ströme cont. Sieb des Eratosthenes • Strom positiver ganzer Zahlen (define (sieve stream) (cons-stream (head stream) (sieve (filter (lambda (x) (not (divisible? x (head stream)))) (tail stream))))) (define (integers-starting-from n) (cons-stream n (integers-starting-from (1+ n)))) (define integers (integers-starting-from 1)) • Strom von Fibonacci-Zahlen (define (fibgen a b) (cons-stream a (fibgen b (+ a b)))) (define primes (sieve (integers-starting-from 2))) (define fibs (fibgen 0 1)) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 241 Implizit definierte Ströme c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 242 Implizit definierte Ströme cont. Hilfsfunktion: Elementweises Addieren zweier Ströme • Strom positiver ganzer Zahlen (define (add-streams s1 s2) (cond ((empty-stream? s1) s2) ((empty-stream? s2) s1) (else (cons-stream (+ (head s1)(head s2)) (add-streams (tail s1) (tail s2)))))) (define ones (cons-stream 1 ones)) (define integers (cons-stream 1 (add-streams ones integers))) • Strom der Fibonacci-Zahlen (define fibs (cons-stream 0 (cons-stream 1 (add-streams (tail fibs) fibs)))) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 243 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 244 Funktionale Programmierung: Haskell Funktionale Programmierung: Scheme • rein funktional • funktional und imperative Elemente • non-strikt • strikt • strenge Typisierung • keine Typisierung • lazy evaluation • eager evaluation (aber: delay) • Listenkomprehension • – • – • Programm-Daten-Äquivalenz • Pattern matching in Definitionen • – c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 245 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), Rein funktionale Sprachen 246 Funktionskomposition als Sprachmittel • in Scheme: • Programmieren ohne Seiteneffekte (define (compose f1 f2) (lambda (x) (f1 (f2 x)))) • Seiteneffekte können Programme schwer zu lesen und schwer zu kompilieren machen • Abwesenheit von Seiteneffekten: – Ausdrücke sind referentiell transparent – unabhängig von Ausführungsordnung – Denken in Gleichungen möglich – Gleichheit zweier Ausdrücke ist nicht zeitabhängig c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 247 • Beispiel für Anwendung: (define (define (define (define (flatten tree) ...) (remove-duplicates list) ...) list-of-atoms (compose remove-duplicates flatten)) nr-of-atoms (compose length list-of-atoms)) ; 1 ]=> tree ;Value 6: ((a (b (c d) e) (b f g) h (c d))) ; 1 ]=> (list-of-atoms tree) ;Value 7: (a e b f g h c d) ; 1 ]=> (nr-of-atoms tree) ;Value: 8 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 248 Algebraische Typen Produkttypen als algebraische Typen Aufzählungstypen (vgl. [S.Thompson, 1999, 14.1]) • Beispiel: • Beispiel: data People = Person Name Age type Name = String type Age = Int data Temp = Cold | Hot data Season = Spring | Summer | Autumn | Winter • Pattern matching zur Definition von Funktionen weather :: Season -> Temp weather Summer = Hot weather _ = Cold • Beispiele: Person "Hans Mueller" 42 Person "Bart Simpson" 12 • Definition von Gleichheit für Temp Cold == Hot == _ == Cold Hot _ = True = True = False c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 249 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), Produkttypen als algebraische Typen cont. 250 Rekursive algebraische Typen • Pattern matching zur Definition von Funktionen • Beispiel: arithmetische Ausdrücke mit ganzen Zahlen showPerson :: People -> String showPerson (Person st n) = st ++ " -- " ++ show n data Expr = Lit Int| Add Expr Expr| Sub Expr Expr • Konstruktor Person ist Funktion vom Typ • Beispiele: Person :: Name -> Age -> People 7 4+7 (7-4)+11 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 251 Lit 7 Add (Lit 4) (Lit 7) Add (Sub (Lit 7)(Lit 4)) (Lit 11) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 252 Rekursive algebraische Typen cont. Rekursive algebraische Typen cont. • Beispiel: Bäume mit ganzen Zahlen • Funktionen mit Hilfe primitiver Rekursion z.B. Auswerten data NTree = eval :: Expr -> Int eval (Lit n) eval (Add e1 e2) eval (Sub e1 e2) NilT | Node Int NTree NTree • Beispiele für Funktionen: = n = (eval e1) + (eval e2) = (eval e1) - (eval e2) sumTree, depth :: NTree -> Int sumTree NilT = 0 sumTree (Node n t1 t2) = n + sumTree t1 + sumTree t2 ... c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 253 Beispiele für Funktionen: cont. c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 254 Notizen ... depth NilT = 0 depth (Node n t1 t2) = 1 + max (depth t1)(depth t2) occurs :: NTree -> Int -> Int occurs NilT p = 0 occurs (Node n t1 t2) p | n==p = 1 + occurs t1 p + occurs t2 p | otherwise = occurs t1 p + occurs t2 p c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 255 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 256 Polymorphe algebraische Typen: Binäre Bäume als polymorphe algebraische Typen Definitionen algebraischer Typen können Typvariable a, b, ... enthalten (vgl. [S.Thompson, 1999, 14.3]) • die Elemente in den Knoten können von bel. Typ sein • Beispiel: • Beispiel: data Pairs a = Pr a a data Tree a = Nil | Node a (Tree a) (Tree a) deriving (Eq, Ord, Show, Read) • einige Ausprägungen: Pr 47 11 :: Pairs Int Pr [] [42] :: Pairs [Int] Pr [] [] :: Pairs [a] • Beispiel: • der eingebaute Listentyp könnte wie folgt definiert sein: depth :: Tree a -> Int depth Nil = 0 depth (Node n t1 t2) = 1 + max (depth t1)(depth t2) data List a = NilList | Cons a (List a) deriving (Eq, Ord, Show, Read) c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 257 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), Binäre Bäume . . . Binäre Bäume . . . • aus Baum Liste ‘kollabieren’ bei Inorder-Traversierung collapse collapse collapse = 258 • mapTree als Funktion höherer Ordnung auf binären Bäumen :: Tree a -> [a] Nil = [] (Node n t1 t2) collapse t1 ++ [n] ++ collapse t2 mapTree :: (a -> b) -> Tree a -> Tree b mapTree f Nil = Nil mapTree f (Node n t1 t2) = Node (f n) (mapTree f t1) (mapTree f t2) • Beispiel: collapse (Node 7 (Node 4 Nil Nil) (Node 1 (Node 1 Nil Nil) Nil)) = [4,7,1,1] c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 259 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 260 Algebraische Typen: Algebraische Typen: Variante für Produkttypen gibt es eine alternative Syntax: • Instanzen werden kreiert mithilfe des Konstruktors und der benannten Selektoren >p1 = Person’ {name = "Paul", age = 21} • Beispiel (vgl. Folie 134): • da der Selektor die jeweilige Komponente eindeutig benennt, ist die Reihenfolge der Angaben beliebig data Person’ = Person’ {name :: Name, age :: Age} deriving (Eq,Show,Read) >p2 = Person’ {age = 12, name = "Pauline"} • die Ausgabe durch die abgeleitete show-Funktion erfolgt aber in der Reihenfolge aus der data-Definition type Name = String type Age = Int • m.a.W.: statt einer ’positionalen’ Kodierung der Komponenten eines Produkttyps haben wir hier eine Kodierung mit benannten Selektoren c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 261 Main> p1 Person’{name="Paul",age=21} Main> p2 Person’{name="Pauline",age=12} c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 262 Algebraische Typen: Variante Algebraische Typen: Variante • mit den benannten Selektorfunktionen kann auf die Werte der Komponenten zugegriffen werden • beachte: bei der Modifikation wird eine modifizierte Kopie der Ausgangsinstanz kreiert, d.h. diese bleibt unverändert Main> name p1 "Paul" Main> age p2 12 Main> p1 Person’{name="Paul",age=21} • Konstruktor und Selektoren können in Funktionen zur Modifikation von Instanzen verwendet werden • Beispiel: Main> hasBirthday p1 Person’{name="Paul",age=22} Main> p1 Person’{name="Paul",age=21} hasBirthday :: Person’ -> Person’ hasBirthday p = Person’ {name= name p, age= age p + 1} c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 263 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 264 Funktionale Programmierung: Haskell Funktionale Programmierung: Scheme • rein funktional • funktional und imperative Elemente • non-strikt • strikt • strenge Typisierung • keine Typisierung • lazy evaluation • eager evaluation (aber: delay) • Listenkomprehension • – • – • Programm-Daten-Äquivalenz • Pattern matching in Definitionen • – c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 265 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), Rein funktionale Sprachen 266 Funktionskomposition als Sprachmittel • in Scheme: • Programmieren ohne Seiteneffekte (define (compose f1 f2) (lambda (x) (f1 (f2 x)))) • Seiteneffekte können Programme schwer zu lesen und schwer zu kompilieren machen • Abwesenheit von Seiteneffekten: – Ausdrücke sind referentiell transparent – unabhängig von Ausführungsordnung – Denken in Gleichungen möglich – Gleichheit zweier Ausdrücke ist nicht zeitabhängig c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 267 • Beispiel für Anwendung: (define (define (define (define (flatten tree) ...) (remove-duplicates list) ...) list-of-atoms (compose remove-duplicates flatten)) nr-of-atoms (compose length list-of-atoms)) ; 1 ]=> tree ;Value 6: ((a (b (c d) e) (b f g) h (c d))) ; 1 ]=> (list-of-atoms tree) ;Value 7: (a e b f g h c d) ; 1 ]=> (nr-of-atoms tree) ;Value: 8 c Prof. Dr. D. Rösner; erstellt: 3. Juli 2007 Sommer 2007, Programmierparadigmen (PGP), 268