Funktionale Programmierung mit Haskell



Ähnliche Dokumente
Einfache Ausdrücke Datentypen Rekursive funktionale Sprache Franz Wotawa Institut für Softwaretechnologie

Funktionale Programmierung

Folge 19 - Bäume Binärbäume - Allgemeines. Grundlagen: Ulrich Helmich: Informatik 2 mit BlueJ - Ein Kurs für die Stufe 12

Objektorientierte Programmierung

Objektorientierte Programmierung für Anfänger am Beispiel PHP

Einführung in die Java- Programmierung

Lineargleichungssysteme: Additions-/ Subtraktionsverfahren

Funktionale Programmierung mit Haskell

Scala kann auch faul sein

Gliederung. Programmierparadigmen. Sprachmittel in SCHEME. Objekte: Motivation. Objekte in Scheme

Javadoc. Programmiermethodik. Eva Zangerle Universität Innsbruck

Erweiterung der Aufgabe. Die Notenberechnung soll nicht nur für einen Schüler, sondern für bis zu 35 Schüler gehen:

Programmierparadigmen

Java: Vererbung. Teil 3: super()

Eine Logikschaltung zur Addition zweier Zahlen

Grundlagen von Python

Generische Record-Kombinatoren mit statischer Typprüfung

Vorkurs C++ Programmierung

Der Aufruf von DM_in_Euro 1.40 sollte die Ausgabe 1.40 DM = Euro ergeben.

Klassenentwurf. Wie schreiben wir Klassen, die leicht zu verstehen, wartbar und wiederverwendbar sind? Objektorientierte Programmierung mit Java

Excel Funktionen durch eigene Funktionen erweitern.

Was bisher geschah. deklarative Programmierung. funktionale Programmierung (Haskell):

Verhindert, dass eine Methode überschrieben wird. public final int holekontostand() {...} public final class Girokonto extends Konto {...

Anzeige des Java Error Stack in Oracle Forms

Java Kurs für Anfänger Einheit 5 Methoden

Grundbegriffe der Informatik

Binäre Bäume. 1. Allgemeines. 2. Funktionsweise. 2.1 Eintragen

Kontrollstrukturen und Funktionen in C

Adressen. Praktikum Funktionale Programmierung Organisation und Überblick. Termine. Studienleistung

Informatik 2 Labor 2 Programmieren in MATLAB Georg Richter

Kapitalerhöhung - Verbuchung

IMAP Backup. Das Programm zum Sichern, Synchronisieren, Rücksichern und ansehen von gesicherten Mails. Hersteller: malu-soft

Informationsblatt Induktionsbeweis

Zweite Möglichkeit: Ausgabe direkt auf dem Bildschirm durchführen:

Skript und Aufgabensammlung Terme und Gleichungen Mathefritz Verlag Jörg Christmann Nur zum Privaten Gebrauch! Alle Rechte vorbehalten!

Praktische Informatik 3: Einführung in die Funktionale Programmierung Vorlesung vom : Rekursive Datentypen

Zeichen bei Zahlen entschlüsseln

Funktionen Häufig müssen bestimmte Operationen in einem Programm mehrmals ausgeführt werden. Schlechte Lösung: Gute Lösung:

Suchmaschinen. Universität Augsburg, Institut für Informatik SS 2014 Prof. Dr. W. Kießling 23. Mai 2014 Dr. M. Endres, F. Wenzel Lösungsblatt 6

Lineare Gleichungssysteme

Fotos in Tobii Communicator verwenden

C/C++ Programmierung

Typdeklarationen. Es gibt in Haskell bereits primitive Typen:

Arge Betriebsinformatik GmbH & Co.KG, CAP News 40, Februar CAP-News 40

Übungen zu Einführung in die Informatik: Programmierung und Software-Entwicklung: Lösungsvorschlag

Erwin Grüner

Zählen von Objekten einer bestimmten Klasse

Robot Karol für Delphi

Einführung in. Logische Schaltungen

XONTRO Newsletter. Makler. Nr. 16

Was ist Logische Programmierung?

II. Grundlagen der Programmierung. 9. Datenstrukturen. Daten zusammenfassen. In Java (Forts.): In Java:

3. LINEARE GLEICHUNGSSYSTEME

Installation der SAS Foundation Software auf Windows

Grammatiken. Einführung

Einführung in die Programmierung

Excel Pivot-Tabellen 2010 effektiv

10.6 Programmier-Exits für Workitems

SEMINAR Modifikation für die Nutzung des Community Builders

Unterrichtsmaterialien in digitaler und in gedruckter Form. Auszug aus: Übungsbuch für den Grundkurs mit Tipps und Lösungen: Analysis

Übungen für Woche 10

Kleines Handbuch zur Fotogalerie der Pixel AG

1. Man schreibe die folgenden Aussagen jeweils in einen normalen Satz um. Zum Beispiel kann man die Aussage:

Kurzeinführung LABTALK

Funktionsbeschreibung. Lieferantenbewertung. von IT Consulting Kauka GmbH

Programmierkurs Java

Constraint-Algorithmen in Kürze - Mit der Lösung zur Path-Consistency-Aufgabe 9

Konzepte der Informatik

Einführung in die C++ Programmierung für Ingenieure

Jede Zahl muss dabei einzeln umgerechnet werden. Beginnen wir also ganz am Anfang mit der Zahl,192.

Funktionale Programmierung mit Haskell

Künstliches binäres Neuron

Grundlagen der Programmierung (Vorlesung 14)

Durchführung der Datenübernahme nach Reisekosten 2011

Primzahlen und RSA-Verschlüsselung

OP-LOG

Switching. Übung 7 Spanning Tree. 7.1 Szenario

Source Code Konverter Online: (VB.net <-> C#) Kommerzielle Produkte (VB, C#, C++, Java) Code Nachbearbeitung in der Praxis...

Besonderheiten von C#

Programmieren Tutorium

SWE1 / Übung 2 ( )

Workshop: Eigenes Image ohne VMware-Programme erstellen

Funktionale Programmierung ALP I. Funktionen höherer Ordnung. Teil 2 SS Prof. Dr. Margarita Esponda. Prof. Dr.

Software Engineering Klassendiagramme Assoziationen

Kapitalerhöhung - Verbuchung

Fehlermonitor. Software zur seriellen Verbindung PC-Airdos Visualdatensignale und Fehlermeldungen-Ausagabe per SMS / Drucker

TECHNISCHE UNIVERSITÄT MÜNCHEN FAKULTÄT FÜR INFORMATIK

4D Server v12 64-bit Version BETA VERSION

Java Einführung Operatoren Kapitel 2 und 3

a n auf Konvergenz. Berechnen der ersten paar Folgenglieder liefert:

Einführung in die Programmierung

4. Lernen von Entscheidungsbäumen. Klassifikation mit Entscheidungsbäumen. Entscheidungsbaum

Programmieren in Haskell Einführung

Bauteilattribute als Sachdaten anzeigen

Theoretische Grundlagen der Informatik

Algorithmen und Datenstrukturen

AZK 1- Freistil. Der Dialog "Arbeitszeitkonten" Grundsätzliches zum Dialog "Arbeitszeitkonten"

Unterprogramme. Funktionen. Bedeutung von Funktionen in C++ Definition einer Funktion. Definition einer Prozedur

Transkript:

Funktionale Programmierung mit Haskell Prof. Dr. Hans J. Schneider Lehrstuhl für Programmiersprachen und Programmiermethodik Friedrich-Alexander-Universität Erlangen-Nürnberg Sommersemester 2011 I. Die Sprache Haskell II. Fallstudien 8. Suchbäume und beliebige Graphen 9. Lambda-Interpretierer 10. Syntaxanalyse 11. Algebraische Spezifikation 12. Semantik operationeller Sprachen 13. Kategorien in Haskell 14. Compilieren funktionaler Programme c Hans J. Schneider 2011

Motivation Philip Wadler: The essence of functional programming (Principles of Programming Languages, 1992) Rein funktional oder nicht? Pure functional languages (Haskell, Miranda) offer the power of lazy evaluation and the simplicity of equational reasoning. Impure functional languages (ML, Scheme) offer a tempting spread of features such as state, exception handling, or continuations. Wadlers Entscheidung hängt davon ab, wie leicht ein Programm geändert werden kann: Pure languages ease change by making manifest the data upon which each operation depends. But, sometimes, a seemingly small change may require a program [... ] to be extensively restructured. Funktionale Programmierung mit Haskell 9.1

Beispiel: Interpretierer für den Lambda-Kalkül Wadlers Beispiel beschränkt sich auf Addition, Funktionsdefinition und Funktionsanwendung. Definition der zu interpretierenden Terme: data Term = Var Name Con Int Add Term Term Lam Name Term App Term Term Weitere Operationen können problemlos eingebaut werden. Die Beschränkung auf den Typ Int erspart die Typprüfung. Die Interpretation ist wesentlich einfacher als die für zuweisungsorientierte Sprachen. (Siehe später!) Funktionale Programmierung mit Haskell 9.2

Kern eines Interpretierers Kern eines Interpretierers: Typ: interp (Var x) e = lookup x e interp (Con i) e = Num i interp (Add u v) e = add (interp u e) (interp v e) interp (Lam x v) e = Fun (\a -> interp v ((x,a):e)) interp (App t u) e = apply (interp t e) (interp u e) interp Wir benötigen noch: :: Term -> Environment -> Value die Basisfunktionen add und apply, die Definitionen von Term, Environment, und Value, kümmern uns darum aber erst in der Fassung mit monadischen Klassen. Funktionale Programmierung mit Haskell 9.3

Wadlers Varianten des Interpretierers To add error handling to it, I need to modify the result type to include error values, and at each recursive call to check for and handle errors appropriately. exception handling To add an execution count to it, I need to modify the result type to include such a count, and modify each recursive call to pass around such counts appropriately. global variable To add an output instruction to it, I need to modify the result type to include an output list, and to modify each recursive call to pass around this list appropriately. side effect Wadlers Vorschlag mit monadischen Klassen: All that is required is to redefine the monad and to make a few local changes. Funktionale Programmierung mit Haskell 9.4

Modifikation der Basiswerte Das Beispiel wird auf ganzzahlige Werte und Funktionen darauf beschränkt: data Value = Wrong Num Int Fun (Value -> M Value) Der Typkonstruktor M erlaubt, das eigentliche (rechnerische) Ergebnis um Zusatzinformationen zu erweitern. Die Werte müssen ausdruckbar sein: instance Show Value where show Wrong = "<wrong>" show (Num i) = show i show (Fun f) = "<function>" Das Ausdrucken der M-Werte muss abhängig von deren Definition geregelt werden. Hinweis: In Wadlers Arbeit werden unterschiedliche Konstruktoren verwandt. Funktionale Programmierung mit Haskell 9.5

Standardversion Value benötigt keine zusätzliche Information (Wadler-Variante 0): newtype M a = M a instance Show a => Show (M a) where show (M a) = show a Allgemeine Definition der monadischen Interpretiererklasse: class InterpMonad m where unit :: a -> m a -- entspricht return bind :: m a -> (a -> m b) -> m b -- entspricht >>= fail :: String -> m a instance InterpMonad M where unit a = M a (M a) bind k = k a fail s = error s -- wird nicht aufgerufen Wadler definiert für jede Version eine eigene Klasse und muss dann auch die Methoden unterschiedlich benennen. Funktionale Programmierung mit Haskell 9.6

Grundlegende Funktionen Operationen auf den Werten: Funktionsanwendung: -- apply :: Value -> Value -> M Value apply (Fun k) a = k a apply notfun a = unit Wrong Arithmetische Operationen: -- add :: Value -> Value -> M Value add (Num i) (Num j) = unit (Num (i+j)) add a b = unit Wrong Umgebung: type Environment = [(Name, Value)] type Name = String -- lookup :: Name -> Environment -> M Value lookup x [] = unit Wrong lookup x ((y,b):e) = if x==y then unit b else lookup x e Funktionale Programmierung mit Haskell 9.7

Interpretation der Terme Die Interpretation eines Termes liefert, abhängig von der Umgebung einen monadischen Wert: -- interp :: Term -> Environment -> M Value Interpretation der Atome: interp (Var x) e = lookup x e interp (Con i) e = unit (Num i) interp (Lam x v) e = unit (Fun (\a -> interp v ((x,a):e))) Interpretation der vordefinierten Operationen: interp (Add u v) e = interp u e bind (\x -> interp v e bind (\y -> add x y)) Interpretation der Funktionsanwendung: interp (App t u) e = interp t e bind (\f -> interp u e bind (\x -> apply f x)) Funktionale Programmierung mit Haskell 9.8

Erinnerung unit verwandelt Value in M Value: unit :: a -> m a unit a = M a interp (Con i) e = unit (Num i) Was macht bind? bind :: m a -> (a -> m b) -> m b (M y) bind k = k y bind gibt einen M Value an eine Funktion Value -> M Value weiter, die einen neuen M Value berechnet. interp (Add u v) e = interp u e bind (\x -> interp v e bind (\y -> add x y)) Funktionale Programmierung mit Haskell 9.9

Beispiele Testfunktion: -- test :: Term -> String test t = show (interp t []) Testbeispiele: twice = (Lam "f" (Lam "x" (App (Var "f") (App (Var "f") (Var "x"))))) increm = (Lam "x" (Add (Var "x") (Con 1))) term12 = (App (App twice increm) (Con 5)) Main> test term12 "7" Main> test increm "<function>" Main> test (App increm (Con 10)) "11" Beispiel mit Fehler: Main> test (App (App twice (Con 3)) (Con 5)) "<wrong>" Funktionale Programmierung mit Haskell 9.10

Interpretierer mit Fehlermeldungen Wir unterscheiden in M Value zwischen erfolgreichen und fehlerbehafteten Berechnungen: data M a = Suc a Err String Fehler werden bis zum Ende der Berechnung durchgereicht: instance InterpMonad M where unit a = Suc a (Suc a) bind k = k a (Err s) bind k = Err s fail s = Err s und schließlich ausgedruckt: instance Show a => Show (M a) where show (Suc a) = "Success: " ++ show a show (Err s) = "Error: " ++ s Man könnte auch auf die Meldung Success verzichten. Funktionale Programmierung mit Haskell 9.11

Notwendige Änderungen interp muss gar nicht geändert werden. lookup muss an einer Stelle geändert werden: lookup x [] = fail("unbound variable: " ++ x) lookup x ((y,b):e) = if x==y then unit b else lookup x e apply muss an einer Stelle geändert werden: apply (Fun k) a = k a apply notfun a = fail("should be function: " ++ show notfun) add muss an einer Stelle geändert werden: add (Num i) (Num j) = unit (Num (i+j)) add a b = fail("should be numbers: " ++ show a ++ ", " ++ show b) Man könnte bei add noch eine Fallunterscheidung bezüglich des nicht korrekten Summanden einfügen. Funktionale Programmierung mit Haskell 9.12

Beispiele term12 = (App (App twice increm) (Con 5)) terme1 = (Add (Var "x") (Con 10)) terme2 = (Add (Con 10) term9) terme4 = (App (App twice (Con 3)) (Con 5)) terme5 = (App (App twice (Con 3)) (Var "x")) Korrektes Beispiel: Main> test term12 "Success: 7" Inkorrekte Beispiele: Main> test terme4 "Error: should be function: 3" Main> test terme1 "Error: unbound variable: x" Main> test terme2 "Error: should be numbers: 10, <function>" Main> test terme5 "Error: unbound variable: x" Funktionale Programmierung mit Haskell 9.13

Interpretierer mit Reduktionszähler Das Zählen der Reduktionen (Anwendung eines λ-ausdruckes auf ein Argument) wird hier als Beispiel für das Mitführen von Zuständen und ihrer Transformation betrachtet. Eine Zustandstransformation geht von einem Zustand aus und liefert einen neuen Zustand, zusammen mit einem Wert: newtype M a = M (State -> (a, State)) Gegenüber der Fassung von Wadler ist hier der Konstruktor M eingefügt. Für das Zählerbeispiel genügt: type State = Int tick = M (\s -> ((), s+1)) Am Ende sollen das Ergebnis und der akkumulierte Zähler ausgedruckt werden: instance (Show a) => Show (M a) where show (M f) = let (a, s) = f 0 in "Value: " ++ show a ++ " Count: " ++ show s Funktionale Programmierung mit Haskell 9.14

Einbettung in die monadische Klasse unit liefert den gegebenen Wert, ohne den Zustand zu ändern.: instance InterpMonad M where unit a = M (\s -> (a, s)) bind :: M a -> (a -> M b) -> M b nimmt eine Zustandstransformation M m und eine Funktion k und transformiert den Anfangszustand s0 mit m zu einem Paar (a,s1): (M m) bind k = M (\s0 -> let (a, s1) = m s0 k erzeugt durch Anwendung auf diesen Wert a eine neue Zustandstransformation M m : (M m ) = k a An diese wird der Zwischenzustand s1 weitergegeben: in m s1 ) fail s = error s -- wird nicht aufgerufen Funktionale Programmierung mit Haskell 9.15

Einfügen des Zählers State ist in dieser Variante ein Zähler: newtype M a = M (State -> (a, State)) tick = M (\s -> ((), s+1)) Wir müssen die Zähloperation in die Interpretationen von add und apply einfügen. add: add (Num i) (Num j) = tick bind (\() -> unit (Num (i+j))) add a b = unit Wrong apply apply (Fun k) a = tick bind (\() -> k a) apply notfun a = unit Wrong Funktionale Programmierung mit Haskell 9.16

Simulation von add add (Num i) (Num j) = tick bind (\() -> unit (Num (i+j))) = M (\s -> ((), s+1)) bind (\() -> unit (Num (i+j))) = M (\s0 -> let (a, s1) = (\s -> ((), s+1) s0 = ((), s0+1) a = () s1 = s0+1 (M m ) = (\() -> unit (Num (i+j))) a = unit (Num (i+j)) = M (\s -> (Num (i+j), s)) m = (\s -> (Num (i+j), s)) in (m s1) = (\s -> (Num (i+j), s)) (s0+1) = (Num (i+j), s0+1) Funktionale Programmierung mit Haskell 9.17

Beispiele term1 = (Add (Con 10) (Con 11)) term4 = (Lam "x" (Add (Var "x") (Var "x"))) term5 = (App term4 term1) Interpretation mit der ursprünglichen Version: *Main> test term5 "42" Interpretation mit der Zähler-Version: *Main> test term5 "Value: 42 Count: 3" Fehlerhafte Beispiele: terme1 = (Add (Var "x") (Con 10)) terme4 = (App (App twice (Con 3)) (Con 5)) *Main> test terme1 "Value: <wrong> Count: 0" *Main> test terme4 "Value: <wrong> Count: 2" Funktionale Programmierung mit Haskell 9.18

Call-by-name-Interpretierer Die bisherigen Lösungen simulieren das Prinzip call-by-value : Das Argument ist ein Wert. Fun:: Value -> M Value Haskell macht das natürlich lazy! Bei Auswertung mit call-by-name muss die Auswertung des Arguments zurückgestellt werden. Das Argument bleibt eine Berechnung: Fun:: M Value -> M Value Notwendige Änderung des Datentyps Value: data Value = Wrong Num Int Fun (M Value -> M Value) Variablen sind jetzt an Berechnungen gebunden: type Environment = [(Name, M Value)] Deshalb genügt in lookup jetzt b statt unit b: lookup x [] = unit Wrong lookup x ((y,b):e) = if x==y then b else lookup x e Funktionale Programmierung mit Haskell 9.19

Änderungen in den Berechnungen Die Auswertung von Konstanten und der Addition ändert sich nicht. Die Änderung bei der Auswertung der Variablen ist bereits in lookup berücksichtigt. Die Auswertung der Lambda-Ausdrücke sieht zwar gleich aus: interp (Lam x v) e = unit (Fun (\a -> interp v ((x,a):e))) interp (Lam x v) e = unit (Fun (\m -> interp v ((x,m):e))) Der Parameter ist jetzt aber vom Typ M Value, nicht mehr vom Typ Value. Die wesentliche Änderung passiert in der Auswertung der Funktionsapplikation: Die Funktion wird ausgewertet, nicht aber das Argument. interp (App t u) e = interp t e bind (\f -> apply f (interp u e)) In diesen Interpretierer können wie zuvor Fehlermeldungen und/oder ein Reduktionszähler durch Anpassung des Typs State eingebaut werden. Funktionale Programmierung mit Haskell 9.20

Zusammenfassung des Call-by-name-Interpretierers Interpretierer: interp :: Term -> Environment -> M Value interp (Var x) e = lookup x e interp (Con i) e = unit (Num i) interp (Add u v) e = interp u e bind (\a -> interp v e bind (\b -> add a b)) interp (Lam x v) e = unit (Fun (\m -> interp v ((x,m):e))) interp (App t u) e = interp t e bind (\f -> apply f (interp u e)) Wadler: An advantage of the monadic style is that the types make clear where effects occur. Thus, one can distinguish call-by-value from call-by-name simply by examining the types. Funktionale Programmierung mit Haskell 9.21

Vergleich Wir betrachten folgendes Beispiel: term1 = (Add (Con 10) (Con 11)) term4 = (Lam "x" (Add (Var "x") (Var "x"))) term5 = (App term4 term1) Ergebnis bei Verwendung des Call-by-name-Interpretierers: *Main> test term5 "Value: 42 Count: 4" Ergebnis bei Verwendung des Call-by-value-Interpretierers: *Main> test term5 "Value: 42 Count: 3" Bei Verwendung des Call-by-value-Interpretierers wird term1 nur einmal ausgewertet. Funktionale Programmierung mit Haskell 9.22

Zustandstransformationen Monadische Klassen können benutzt werden, um Programme zu formulieren, die interne Zustände benutzen: data State s a = ST (s -> (a, s)) (M.P. Jones: Lect. Notes Comp. Sc. 925, 1995) Einbettung in die vordefinierte Klasse Monad: instance Monad (State s) where return x = ST (\s -> (x,s)) -- entspricht unserem unit m >>= f = ST (\s -> let ST m = m (x, s1) = m s ST f = f x in f s1 ) -- entspricht unserem bind Passender Funktor: instance Functor (State s) where fun f (ST st) = ST (\s -> let (x, s ) = st s in (f x, s ) ) Funktionale Programmierung mit Haskell 9.23