Deklarative Programmierung Prof. Dr. Sibylle Schwarz HTWK Leipzig, Fakultät IMN Gustav-Freytag-Str. 42a, 04277 Leipzig Zimmer Z 411 (Zuse-Bau) http://www.imn.htwk-leipzig.de/~schwarz [email protected] Sommersemester 2015
Deklarative Programmierung
Prof. Dr. Sibylle SchwarzHTWK Leipzig, Fakultät IMN
Gustav-Freytag-Str. 42a, 04277 LeipzigZimmer Z 411 (Zuse-Bau)
http://www.imn.htwk-leipzig.de/[email protected]
Sommersemester 2015
Motivation
. . . there are two ways of constructing a software design:One way is to make itso simple that there are obviously no deficienciesand the other way is to make itso complicated that there are no obvious deficiencies.The first method is far more difficult.
Tony Hoare, 1980 ACM Turing Award Lecture
Programmierparadigmen
Abstraktionsstufen (zeitliche Entwicklung):I Programm = MaschinencodeI menschenlesbare symbolische Darstellung (Assembler)I Beschreibung von Programmablauf- und Datenstrukturen
(imperative und objektorientierte Sprachen)I Beschreibung der Aufgabenstellung
(deklarative Sprachen, z.B. funktional, logisch, Constraints)
Unterschied besteht darin, wie detailliert das Programm dasLösungsverfahren beschreiben muss.
Formen der deklarativen ProgrammierungGrundidee:Jedes Programm ist ein mathematisches Objekt mit einer bekanntenwohldefinierten Semantikfunktionale Programmierung (z.B. Haskell, ML, Lisp):
Programm: Menge von Funktions-Definitionen(Gleichungen zwischen Termen)
Ausführung: Pattern matching, Reduktion (Termersetzung)logische Programmierung (Prolog):
Programm: Menge logischer Formeln (Horn-Klauseln)Ausführung: Unifikation, SLD-Resolution
funktional-logische Programmierung (z.B. Mercury, Curry):Kombination funktionaler und logischer Konzepte
Constraint-Programmierung:Programm: Menge von Constraints
(z.B. Gleichungen, Ungleichungen, logische Formeln)Ausführung: Constraint-Löser (abhängig vom Constraint-Bereich)Beispiele: Constraints: Menge linearer Gleichungen
Constraint-Löser: Gauß-AlgorithmusConstraints: aussagenlogische CNFConstraint-Löser: SAT-Solver
Beispiele
I funktionale Programmierung: foldr (+) 0 [1,2,3]
foldr f z l = case l of[] -> z ; (x:xs) -> f x (foldr f z xs)
I logische Programmierung: append(A,B,[1,2,3]).append([],YS,YS).append([X|XS],YS,[X|ZS]):-append(XS,YS,ZS).
I Constraint-Programmierung(set-logic QF_LIA) (set-option :produce-models true)(declare-fun a () Int) (declare-fun b () Int)(assert (and (>= a 5) (<= b 30) (= (+ a b) 20)))(check-sat) (get-value (a b))
Deklarative vs. imperative Programmierungdeklarativ (beschreibend)
Programm: Repräsentation einer AufgabeProgrammelemente: Ausdrücke (Terme),
Formeln, GleichungenProgrammierung: Modellierung der AufgabeAusführung: Lösung des beschriebenen Problems
durch Standardverfahren z.B.logisches Schließen,Umformung von Ausdrücken
imperativ zustandsorientiert (von-Neumann-Typ)Programm: Repräsentation eines AlgorithmusProgrammelemente: Ausdrücke und AnweisungenProgrammierung: Modellierung eines Verfahrens
zur Lösung einer AufgabeAusführung des Lösungsverfahrens durch
schrittweise Zustandsänderungen(Speicherbelegung)
Definition
deklarativ: jedes (Teil-)Programm/Ausdruck hat einen Wert
. . . und keine weitere (versteckte) Wirkung.
Werte können sein:I „klassische“ Daten (Zahlen, Listen, Bäume. . . )I Funktionen (Sinus, . . . )I Aktionen (Datei schreiben, . . . )
Softwaretechnische Vorteile
der deklarativen Programmierung:
Beweisbarkeit : Rechnen mit Programmen wie in derMathematik mit Termen
Sicherheit : es gibt keine Nebenwirkungenund Wirkungen sieht man bereits am Typ
Wiederverwendbarkeit : durch Entwurfsmuster(= Funktionen höherer Ordnung)
Effizienz : durch Programmtransformationen im CompilerParallelisierbarkeit : durch Nebenwirkungsfreiheit
Beispiel Spezifikation/Test
import Test.SmallCheck
append :: [t] -> [t] -> [t]append x y = case x of
[] -> yh : t -> h : append t y
associative f =\ x y z -> f x (f y z) == f (f x y) z
test1 = smallCheck(associative (append::[Int]->[Int]->[Int]))
Übung: Kommutativität (formulieren und testen)
Beispiel Verifikation
app :: [t] -> [t] -> [t]app x y = case x of
[] -> yh : t -> h : app t y
zu beweisen:
app x (app y z) == app (app x y) z
Beweismethode: Induktion nach x.I Induktionsanfang: x == [] . . .I Induktionsschritt: x == h : t . . .
Deklarative Programmierung in der Lehre
funktionale Programmierung: diese Vorlesunglogische Programmierung: in LV Künstliche Intelligenz
Constraint -Programmierung: als Master-Wahlfach
Beziehungen zu weiteren LV: VoraussetzungenI Bäume, Terme (Alg.+DS, TGI)I Logik (TGI, Digitaltechnik, Softwaretechnik)
Anwendungen:I SoftwarepraktikumI weitere Sprachkonzepte in LV Prinzipien v.
ProgrammiersprachenI LV Programmverifikation (vorw. f. imperative Programme)
Gliederung der Vorlesung
I Terme, TermersetzungssystemeI algebraische Datentypen, Pattern Matching,I Rekursive Datenypen, RekursionsschemataI Funktionen (polymorph, höherer Ordnung), Lambda-KalkülI Typklassen zur Steuerung der PolymorphieI Bedarfsauswertung, unendl. Datenstrukturen
Organisation der Lehrveranstaltung
I jede Woche eine VorlesungI Hausaufgaben:
I schriftliche Übungen,I autotool
I jede Woche eine Übung / PraktikumI Beispiele,I Besprechung der schriftlichen Aufgaben,I autotool
Prüfungsvorleistung:regelmäßiges (d.h. innerhalb der jeweiligen Deadline) underfolgreiches (ingesamt ≥ 50% der Pflichtaufgaben) Bearbeitenvon Übungsaufgaben.
Prüfung: Klausur (ohne Hilfsmittel)
LiteraturSkript voriges Semester:http://www.imn.htwk-leipzig.de/~waldmann/edu/ss14/fop/folien/mainFolien aktuelles Semester:http://www.imn.htwk-leipzig.de/~schwarz/lehre/ss15/dpBücher:
I Graham Hutton: Programming in Haskell, Cambridge 2007I Klassiker (englisch):http://haskell.org/haskellwiki/Books
I deutsch:I Peter Pepper und Petra Hofstedt:
Funktionale Programmierung. Sprachdesign undProgrammiertechnik Springer 2006
I Manuel Chakravarty und Gabriele Keller:Einführung in die Programmierung mit HaskellPearson 2004
online: http://www.haskell.org/Informationen, Download, Dokumentation, Tutorials, . . .
Werkzeug und StilDie Grenzen meiner Sprache bedeuten die Grenzen meinerWelt.
Ludwig Wittgenstein
speziell in der Informatik:We are all shaped by the tools we use, in particular: theformalisms we use shape our thinking habits, for better or forworse, and that means that we have to be very careful in thechoice of what we learn and teach, for unlearning is not reallypossible.(Many years ago, if I could use a new assistant, oneprerequisite would be No prior exposure to FORTRAN", and athigh schools in Siberia, the teaching of BASIC was notallowed.)
Edsger W. Dijkstra
aus E. W. Dijkstra Archivehttp://www.cs.utexas.edu/~EWD/
Konzepte und Sprachen
Funktionale Programmierung ist ein Konzept.
Realisierungen:I in prozeduralen Sprachen:
I Unterprogramme als Argumente (in Pascal)I Funktionszeiger (in C)
I in OO-Sprachen: BefehlsobjekteI Multi-Paradigmen-Sprachen:
I Lambda-Ausdrücke in C#, Scala, ClojureI funktionale Programmiersprachen (LISP, ML, Haskell)
Die Erkenntnisse sind sprachunabhängig.
I A good programmer can write LISP in any language.I Learn Haskell and become a better Java programmer.
Geschichte
ab ca. 1930 Alonzo Church λ-Kalkülab ca. 1950 John McCarthy LISPab ca. 1960 Peter Landin ISWIMab ca. 1970 John Backus FP
Robin Milner MLDavid Turner Miranda
ab 1987 Haskell
Warum Haskell?
I deklarativ, Nähe zum (mathematischen) ModellI keine Nebenwirkungen (klare Semantik)I Funktionen sind Daten (Funktionen höherer Ordnung)I starkes TypsystemI TypklassenI lazy evaluation (ermöglicht Rechnen mit unendlichen
Datenstrukturen)I kompakte Darstellung (kurze Programme)I Modulsystem
Entwicklung von Haskell-Programmen
Haskell-Interpreter: ghci, Hugs
Haskell-Compiler: ghc
Entwicklungsumgebungen:I http://leksah.org/
I http://eclipsefp.sourceforge.net/
I http://www.haskell.org/visualhaskell/
alles kostenlos und open source
Real Programmers (http://xkcd.com/378/)
Wiederholung: Terme
Signatur (funktional)Σ (ΣF ) ist Menge von Funktionssymbolen mitStelligkeiten
Term t = f (t1, . . . , tk ) in Signatur Σ istI Funktionssymbol der Stelligkeit k :
(f , k) ∈ Σ der Stelligkeit kmit Argumenten t1, . . . , tk , die selbst Termesind.
Term(Σ,X) = Menge aller Terme über Signatur Σ mitIndividuenvariablen aus X
Graphentheorie: ein Term ist eingerichteter, geordneter, markierter Baum
Datenstrukturen:I Funktionssymbol = Konstruktor,I Term = Baum
Beispiele: Signatur, Terme
I Signatur: Σ1 = {Z/0,S/1, f/2}Elemente aus Term(Σ1):Z (), S(S(Z ())), f (S(S(Z ())),Z ())
I Signatur: Σ2 = {E/0,A/1,B/1}Elemente aus Term(Σ2): . . .
Haskell-Programme
Programm: Menge von Funktions-DefinitionenGleichungen zwischen Termen
Ausdruck: TermAusführung: Auswertung des Ausdruckes (Bestimmung seines
Wertes)Pattern matching, Reduktion, (Termersetzung)
Semantik:Funktion von Eingabe (Ausdruck) auf Ausgabe (Wert)
I keine Variablen, also keine Programmzustände(kein Aufruf-Kontext)
I Wert jeder Funktion(sanwendung) hängt ausschließlichvon den Werten der Argumente ab
Syntax
Ausdrücke : Termez.B. 2 + x * 7 oder double 2
Funktionsdefinition : Gleichung zwischen zwei Ausdrückenz.B. inc x = x + 1
Programm :I Folge (Liste) von FunktionsdefinitionenI Ausdruck
Ausdrücke
Ausdruck = Term (Baumstruktur)
Jeder Ausdruck hatI einen Typ undI einen Wert
Berechnung des Wertes durch schrittweise Reduktion(Termersetzung)
Beispiele
Ausdruck 7 hatI den Typ Int
I den Wert 7
Ausdruck 3 * 7 + 2 hatI den Typ Int
I den Wert . . .
Reduktion : (rekursive) Berechnung des Wertes
Funktionsdeklarationen
double :: Int -> Int (Typdeklaration)double x = x + x (Funktionsdefinition)
Ausdruck double 3 hatI den Typ Int
I den Wert 6
Ausdruck double (double 3) hatI den Typ Int
I den Wert . . .
Ausdruck double hatI den Typ Int -> Int
I den Wert x 7→ x + x (mathematische Notation)λx .(x + x) (λ-Kalkül)
Was bisher geschah
I deklarative ProgrammierungI funktional:
Programm: Menge von Termgleichungen, TermAuswertung: Pattern matching, Termumformungen
I logisch:Programm: Menge von Regeln (Horn-Formeln), FormelAuswertung: Unifikation, Resolution
I funktionale Programmierung in Haskell:I nebenwirkungsfreiI lazy evaluation (ermöglicht unendliche Datentypen)I kompakte Darstellung
I Praktikum: Termersetzung, ghci, Prelude
Bezeichnungen für Teilterme
Position : Folge von natürlichen Zahlen(bezeichnet einen Pfad von der Wurzel zu einemKnoten)Beispiel: für Signatur Σ = {(g,2), (f ,1), (c,0)}und Term t = f (g(f (f (c)), c)) ∈ TermΣ, ∅ist [0,1] eine Position in t ,aber [1], [0,1,0], [0,0,1] nicht
Pos(t) Menge aller Positionen des Terms t ∈ Term(Σ,X)(rekursive) Definition: für t = f (t1, . . . , tk )gilt Pos(t) ={[]} ∪ {[i − 1] ++{p | i ∈ {1, . . . , k} ∧ p ∈ Pos(ti)}.
dabei bezeichnen:I [] die leere Folge,
I [i] die Folge der Länge 1 mit Element i ,
I ++ den Verkettungsoperator für Folgen
Operationen mit (Teil)Termen
t [p] : Teilterm von t an Position pBeispiele:
I f (g(f (f (c)), c))[0,0] = f (f (c))I f (g(f (f (c)), c))[0,1] = . . .
(induktive) Definition (über die Länge von p):IA p = [] : t [] = tIS p = i ++p′: f (t1, . . . , tn)[p] = ti [p′]
t [p := s] : wie t , aber mit Term s statt t [p] an Position pBeispiele:
I f (g(f (f (c)), c))[[0,0] := c] = f (g(c, c))I f (g(f (f (c)), c))[[0,1] := f (c)] = . . .
(induktive) Definition (über die Länge von p): . . .
Operationen mit Variablen in Termen
I Menge Term(Σ,X) aller Terme über Signatur Σ mitVariablen aus XBeispiel: Σ = {Z/0,S/1, f/2},X = {y},f (Z (), y) ∈ Term(Σ,X).
I Substitution σ: partielle Abbildung X→ Term(Σ,X)Beispiel: σ1 = {(y ,S(Z ()))}
I eine Substitution auf einen Term anwenden: tσ:Intuition: wie t , aber statt v ∈ X immer σ(v)Beispiel: f (Z (), y)σ1 = f (Z (),S(Z ()))Definition durch Induktion über t
Termersetzungssysteme
Daten : Terme (ohne Variablen)Regel : Paar (l , r) von Termen mit Variablen
Programm R: Menge von RegelnBsp: R = {(f (Z (), y), y), (f (S(x), y),S(f (x , y)))}
Relation→R : Menge aller Paare (t , t ′) mitI es existiert (l , r) ∈ RI es existiert Position p in tI es existiert Substitutionσ : (var(l) ∪ var(r))→ Term(Σ)
I so dass t [p] = lσ und t ′ = t [p := rσ].
Termersetzungssysteme als Programme
I →R beschreibt einen Schritt der Rechnung von R,I transitive Hülle→∗R beschreibt Folge von Schritten.I Resultat einer Rechnung ist Term in R-Normalform
(ohne→R-Nachfolger)
Dieses Berechnungsmodell ist im allgemeinennichtdeterministisch R1 = {C(x , y)→ x ,C(x , y)→ y}
(ein Term kann mehrere→R-Nachfolger haben,ein Term kann mehrere Normalformen erreichen)
nicht terminierend R2 = {p(x , y)→ p(y , x)}(es gibt eine unendliche Folge von→R-Schritten,es kann Terme ohne Normalform geben)
Konstruktor-SystemeFür TRS R über Signatur Σ:Symbol s ∈ Σ heißt
definiert , wenn ∃(l , r) ∈ R : l[] = s(. . .)
Konstruktor , sonst
Das TRS R heißt Konstruktor-TRS, fallsdie definierten Symbole links nur in den Wurzeln vorkommen(rechts egal)
Übung: diese Eigenschaft formal spezifizieren
Beispiele:I R1 = {a(b(x))→ b(a(x))} über Σ1 = {a/1,b/1},I R2 = {f (f (x , y), z)→ f (x , f (y , z))} über Σ2 = {f/2}:
definierte Symbole? Konstruktoren? Konstruktor-System?
Funktionale Programme sind ähnlich zu Konstruktor-TRS.
Selbsttest-Übungsaufgaben
zur Klausur-Vorbereitung (statt Praktikum diese Woche) zu
I SignaturenI TermenI SubstitutionenI TermersetzungsysstemenI Normalformen
unterhttp://www.imn.htwk-leipzig.de/~waldmann/edu/ss14/fop/folien/main/node28.html
Funktionale Programme. . . sind spezielle Term-Ersetzungssysteme.
Beispiel:Signatur: S einstellig, Z nullstellig, f zweistellig.Ersetzungssystem {f (Z , y)→ y , f (S(x), y)→ S(f (x , y))}.Startterm f (S(S(Z )),S(Z )).
entsprechendes funktionales Programm:
data N = Z | S N
f :: N -> N -> Nf x y = case x of
{ Z -> y ; S x’ -> S (f x’ y) }
Aufruf: f (S (S Z)) (S Z)
Auswertung = Folge von Ersetzungsschritten→∗RResultat = Normalform (hat keine→R-Nachfolger)
data und case
typisches Vorgehen beim Programmieren einer Funktion
f :: T -> ...
Für jeden Konstruktor des Datentyps
data T = C1 ...| C2 ...
schreibe einen Zweig in der Fallunterscheidung
f x = case x ofC1 ... -> ...C2 ... -> ...
Peano-Zahlen
data N = Z | S Nderiving Show
plus :: N -> N -> Nplus x y = case x of
Z -> yS x’ -> S (plus x’ y)
Beispiel (Tafel): Multiplikation
Was bisher geschah
I Wiederholung Signatur, TermI Termersetzungssysteme (TRS)I Konstruktoren, definierte SymboleI Konstruktor-SystemeI funktionale Programmierung
Programm: Menge von Termgleichungen (TRS)Ausdruck (dessen Wert zu bestimmen ist): Term
Auswertung: Pattern matching, TermumformungenI Haskell:
I nebenwirkungsfreiI kompakte Darstellung
I Praktikum: ghci, Prelude, Typen, Hoogle
Vordefinierte Haskell-Datentypen
einfache Datentypen, z.B.Int ganze Zahlen (feste Länge)Integer ganze Zahlen (beliebige Länge)Bool Wahrheitswerte (False, True)Char ASCII-SymboleFloat, Double
zusammengesetzt (Typkonstruktoren):I Tupel (a, b), (a, b, c), (a1, a2, ...)
z.B. (1, True, ’B’) :: (Int, Bool, Char)
I Listen (polymorph) [a],z.B. [3,5,2] :: [Int],[[’I’, ’N’],[’B’]] :: [[Char]]
I String = [Char], z.B. "INB" = [’I’,’N’,’B’]
Definition von Funktionen
Programmstrukturen:I Verzweigung (Fallunterscheidung)I Rekursion
Beispiel:
sumto :: Int -> Int
sumto n = if n < 0then 0else n + sumto (n-1)
Funktionsdeklarationen (Wiederholung)add :: Int -> Int -> Int (Typdeklaration)add x y = x + y (Funktionsdefinition)Ausdruck add 3 5 hat
I den Typ IntI den Wert 8
Ausdruck add (add 3 5) 1 hatI den Typ IntI den Wert . . .
Ausdruck add hatI den Typ Int -> Int -> IntI den Wert (x , y) 7→ x + y (mathematische Notation)λx .λy .(x + y) (λ-Kalkül)
Ausdruck add 3 hatI den Typ Int -> IntI den Wert y 7→ 3 + y (mathematische Notation)λy .(3 + y) (λ-Kalkül)
(partielle Anwendung von add)
Typinferenz
Typinferenzregel:f :: A→ B e :: A
f e :: B
Man bemerke die Analogie zur logischen Inferenzregel
Modus Ponens:A→ B A
B
Beispiel: Typ von add 3, add 3 5
Beispiele Typinferenz
True :: BoolFalse :: Bool
neg :: Bool -> Boolneg True = Falseneg False = True
Typ von neg True, neg (neg True)
len :: [a] -> Intgerade :: Int -> Bool
Typ von[1,2,3], len [1,2,3], gerade ( len [1,2,3] )
CurryingIdee:Jede Funktion mit mehreren Argumenten lässt sich alsgeschachtelte Funktionen mit je einem Argument auffassen(und aufschreiben)Beispiel:Die folgenden Zeilen definieren dieselbe Funktion vom Typg :: Int -> Int -> Bool
I g m n = m < n
I g m = \ n -> m < n (g m) = λn.(m < n)
I g = \ m n -> m < n g = λm.λn.(m < n)
mit Argument-Tupel (Achtung anderer Typ):g’ :: (Int, Int) -> Boolg’ (m, n) = m < nin mathematischer Notation:zweistellig: C(A×B) ist isomorph zu (CB)A
(n − 1)-stellig: A(A1×···×An−1)n ist isomorph zu
(· · ·(
AAn−1n
)· · ·)A1
Konstruktion zusammengesetzter Datentypen
Operationen:I (kartesisches) ProduktI Vereinigung (Fallunterscheidung)
z.B. AufzählungstypenI Rekursion, z.B. Listen, Bäume, Peano-ZahlenI Potenz, Funktionen
Algebraische Datentypen
data Foo = Foo { bar :: Int, baz :: String }deriving Show
Bezeichnungen (benannte Notation):I data Foo ist TypnameI Foo { .. } ist KonstruktorI bar, baz sind Komponenten
x :: Foox = Foo { bar = 3, baz = "hal" }
Bezeichnungen (positionelle Notation)
data Foo = Foo Int Stringy = Foo 3 "bar"
Mathematisch: ProduktFoo = Int × String
Datentyp mit mehreren Konstruktoren
Beispiel (selbst definiert):
data T = A { foo :: Int }| B { bar :: String }
deriving Show
Beispiel (in Prelude vordefiniert)
data Bool = False | True
data Ordering = LT | EQ | GT
Mathematisch: (disjunkte) VereinigungBool = { False } ∪ { True }
Fallunterscheidung, Pattern Matching
data T = A { foo :: Int }| B { bar :: String }
Fallunterscheidung:
f :: T -> Intf x = case x of
A {} -> foo xB {} -> length $ bar x
Pattern Matching (Bezeichner n,l werden lokal gebunden):
f :: T -> Intf x = case x of
A { foo = n } -> nB { bar = l } -> length l
Rekursive DatentypenWiederholung Peano-Zahlen:
data Nat = Z| S Nat
Menge aller Peano-Zahlen: Nat = {Z} ∪ {Sn | n ∈ Nat}Addition:
add :: Nat -> Nat -> Natadd Z y = yadd ( S x ) y = S ( add x y )
oder
add :: Nat -> Nat -> Natadd x y = case x of
Z -> yS x’ -> S ( add x’ y )
Definition weiterer Operationen: Multiplikation, Potenz
Wiederholung ADT Nat
Sorten: N (natürliche Zahlen)Signatur: Z :: N
S :: N -> Nadd :: N -> N -> Nmult :: N -> N -> N...
Axiome: ∀x ∀y ∀u:add Z x = x = add x Zadd x y = add y xadd x ( add y u ) = add ( add x y ) umult Z x = Z = mult x Zmult ( S Z ) x = x = mult x ( S Z )mult x y = mult y xmult x ( mult y u ) = mult ( mult x y ) u. . .
Nachweis durch strukturelle Induktion (Tafel)
Wiederholung Strukturelle InduktionInduktive Definition strukturierter Daten (rekursive Datentypen):
IA: BasisfälleIS: rekursive Fälle, Vorschrift zur Konstruktion
zusammengesetzter Daten
Induktive Definition von Funktionen über strukturierten Daten:
IA: Definition des Funktionswertes für BasisfälleIS: Berechnung des Funktionswertes der
zusammengesetzten Daten aus den Funktionswertender Teile
Prinzip der strukturellen Induktionzum Nachweis einer Aussage A über strukturierte Daten:
IA: Nachweis, dass A für alle Basisfälle giltIS: I Hypothese (Voraussetzung): A gilt für Teildaten
I Behauptung: A gilt für aus Teildatenzusammengesetzte Daten
I Induktionsbeweis: Nachweis, dass Behauptungaus Hypothese folgt.
Was bisher geschah
I Deklarative vs. imperative ProgrammierungI Funktionale Programmierung:
Programm: Menge von Gleichungen von Termen(Konstruktor-System)
Ausdruck hat Typ und Wert (zu berechnen)Ausführung: Pattern matching, Termersetzung
Haskell:I Algebraische Datentypen
und Pattern MatchingI Rekursive Datentypen (Peano-Zahlen)I Rekursive FunktionenI strukturelle Induktion
Wiederholung Haskell-Typen
I vordefinierte Typen,z. B. Bool, Char, Int, Float, String, ...
I Typvariablen, z.B. a, b ,...I Konstruktion zusammengestzter Typen:
I selbstdefiniert constr typ1 ... typnI Listen-Konstruktor [ typ ]I Tupel-Konstruktor ( typ1, ..., typn )I Funktions-Konstruktor typ1 -> typ2
Algebraische Datentypen – WiederholungOperationen:
I Produkt A× BBeispiel:
data Punkt = Punkt { x :: Float, y :: Float}data Kreis = Kreis { mp :: Punkt, radius :: Float }
I (disjunkte) Vereinigung A ∪ BBeispiel Wahrheitswerte (vordefiniert)
data Bool = True | False
data Shape = Circle { mp :: Punkt, radius :: Float }| Rect { ol, ur :: Punkt}
umfang :: Shape -> Floatumfang s = case s of
Circle {} -> 2 * pi * ( radius s )Rect ol ur -> ...
I Potenz AB = {f : B → A}z.B. gerade_laenge :: String -> Bool
Algebraische Datentypen – Beispiel
data HR = N | O | S | Wdata Turn = Links | Rechts | Um
dreh :: Turn -> HR -> HRdreh Rechts x = case x of
N -> OO -> SS -> WW -> N
dreh Links x = ...
drehs :: [ Move ] -> HR -> HRdrehs ( m : ms ) x = dreh m ( drehs ms x )
Algebraische Datentypen – Beispieledata Pair a b = Pair a b (Produkt)
data Either a b = Left a | Right b (Vereinigung)
data Maybe a = Nothing | Just a (Vereinigung)
Binärbäume (rekursiv):
data Bin a = Leaf| Branch (Bin a) a (Bin a)
Spezialfall Listen (Unärbäume):
data List a = Nil | Cons a (List a)
Bäume (mit beliebigen Knotengraden):
data Tree a = Node a (List (Tree a))
Typsynonyme(Um-)Benennung vorhandener Typen (meist als Kurzform)
Beispiel:
type String = [ Char ]type Name = Stringtype Telefonnummer = Inttype Telefonbuch = [ ( Name , Telefonnummer ) ]
nummern :: Name -> Telefonbuch -> [ Telefonnummer ]nummern name [] = []nummern name ( ( n , t ) : rest ) ...
allgemeiner: Wörterbücher
type Woerterbuch a b = [ ( a, b ) ]
rekursive Typen sind nicht als Typsynonym definierbar
Typsynonyme – BeispielZwei-Personen-Brettspiel (auf rechteckigem Spielfeld)
I Spieler ziehen abwechselndI Jeder Spieler hat Spielsteine seiner Farbe auf mehreren
Positionen des Spielfeldes
Spielfeld:
type Feld = ( Int, Int )type Belegt = [ Feld ]type Spieler = Bool
Spielzustand:
type Zustand = ( Belegt, Belegt, Spieler )
Spiel:
type Spiel = [ Zustand ]
Polymorphienicht polymorphe Typen:tatsächlicher Argumenttyp muss mit dem deklariertenArgumenttyp übereinstimmen:
f :: A→ B e :: A(f e) :: B
polymorphe Typen:Typ von f :: A -> B und Typ von e :: A’ könnenTypvariablen enthalten.A und A’ müssen unfizierbar (eine gemeinsame Instanzbesitzen) aber nicht notwendig gleich sein.σ = mgu(A,A′) allgemeinster UnifikatorTyp von f wird dadurch spezialisiert auf σ(A)→ σ(B)
Typ von e wird dadurch spezialisiert auf σ(A′)allgemeinster Typ von ( f e ) ist dann σ(B)
Wiederholung SubstitutionenSubstitution: partielle Funktion θ : X → Term(Σ,X )
Notation als Aufzählung [x 7→ t1, y 7→ t2, . . .]Anwendung einer Substitution:
I s[x 7→ t ] ist der Term, welcher aus dem Term s durch Ersetzungjedes Vorkommens der Variable x durch t entsteht
I ϕ[x 7→ t ] ist die Formel, die aus der Formel ϕ durch Ersetzungjedes freien Vorkommens der Variable x durch t entsteht
Beispiele:
I g(x , f (a))[x 7→ b] = g(b, f (a))
I h(y , x , f (g(y ,a)))[x 7→ g(a, z), y 7→ a] = h(a,g(a, z), f (g(a,a)))
I g(x , f (a))[x 7→ b, y 7→ a] = g(b, f (a))
I g(b, f (y))[x 7→ b, y 7→ a] = g(b, f (a))
I für θ = [x 7→ b], σ = [y 7→ f (a)] (auch θ(x) = b, σ(y) = f (a) ) gilt(h((b, f (y)), k(x)))θσ = σ(θ(h((b, f (y)), k(x)))= σ(h((b, f (y)), k(b)) = h((b, f (f (a))), k(b))
UnifikatorSubstitution θ heißt genau dann Unifikator der Termet1 und t2 (θ unifiziert t1 und t2), wenn θ(t1) = θ(t2) gilt.Beispiele:
1. θ = [x 7→ b, y 7→ a] unifiziert t1 = g(x , f (a)) und t2 = g(b, f (y))
2. [x 7→ g(g(y)), z 7→ g(y)] unifiziert f (x ,g(y)) und f (g(z), z) (undf (g(z),g(y)).
3. [x 7→ g(g(a)), y 7→ a, z 7→ g(a)]unifiziert f (x ,g(y)) und f (g(z), z).
4. [x 7→ g(g(y)), z 7→ g(y), v 7→ f (a)]unifiziert f (x ,g(y)) und f (g(z), z).
Terme t1, t2 heißen genau dann unifizierbar,wenn ein Unifikator für t1 und t2 existiert.Beispiele:
1. g(x , f (a)) und g(b, f (y)) sind unifizierbar,f (g(a, x)) und f (g(f (x),a)) nicht.
2. h(a, f (x),g(a, y)) und h(x , f (y), z) sind unifizierbar,h(f (a), x) und h(x ,a) nicht.
Was bisher geschah
I Deklarative vs. imperative ProgrammierungI Funktionale Programmierung:
Programm: Menge von Gleichungen von Termen(Konstruktor-System)
Ausdruck hat Typ und Wert (zu berechnen)Ausführung: Pattern matching, Termersetzung
Haskell:I Algebraische Datentypen
und Pattern MatchingI Rekursive Datentypen (Peano-Zahlen)I Rekursive FunktionenI strukturelle Induktion
Typ-Inferenz in Haskell
Inferenzregel:f :: A→ B e :: A
(f e) :: B
für polymorphe Typen:
f :: A→ B e :: A′
(f e) ::?
Unifikator σ der Typausdrücke (Terme) A und A′
(Substitution mit σ(A) = σ(A′))
f :: σ(A)→ σ(B) e :: σ(A′)(f e) :: σ(B)
Wiederholung UnifikatorSubstitution θ heißt genau dann Unifikator der Termet1 und t2 (θ unifiziert t1 und t2), wenn θ(t1) = θ(t2) gilt.Beispiele:
1. θ = [x 7→ b, y 7→ a] unifiziert t1 = g(x , f (a)) und t2 = g(b, f (y))
2. [x 7→ g(g(y)), z 7→ g(y)] unifiziert f (x ,g(y)) und f (g(z), z) (undf (g(z),g(y)).
3. [x 7→ g(g(a)), y 7→ a, z 7→ g(a)]unifiziert f (x ,g(y)) und f (g(z), z).
4. [x 7→ g(g(y)), z 7→ g(y), v 7→ f (a)]unifiziert f (x ,g(y)) und f (g(z), z).
Terme t1, t2 heißen genau dann unifizierbar,wenn ein Unifikator für t1 und t2 existiert.Beispiele:
1. g(x , f (a)) und g(b, f (y)) sind unifizierbar,f (g(a, x)) und f (g(f (x),a)) nicht.
2. h(a, f (x),g(a, y)) und h(x , f (y), z) sind unifizierbar,h(f (a), x) und h(x ,a) nicht.
(Keine) Ordnung auf Unifikatoren
Für zwei Unifikatoren σ, θ der Terme s, t gilt:
Relation R auf Substitutionen:
(σ, θ) ∈ R gdw. ∃ρ : σ ◦ ρ = θ
(Man bemerke die Analogie zur Teilerrelation)
Beispiele:I ([x 7→ y ], [x 7→ a, y 7→ a]) ∈ RI ([x 7→ y ], [y 7→ x ]) ∈ RI ([y 7→ x ], [x 7→ y ]) ∈ R
Diese Relation R ist reflexiv und transitiv, aber nichtantisymmetrisch.
Ordung auf Unifikatoren
σ heißt genau dann allgemeiner als θ, wenn eine Substitution ρ(die nicht nur Umbenennung ist) existiert, so dass σ ◦ ρ = θ
Diese Relation ist eine Halbordnung
Beispiele: Unifikatoren für f (x ,g(y)), f (g(z), z)
1. Unifikator [x 7→ g(g(y)), z 7→ g(y)] ist allgemeiner als[x 7→ g(g(a)), z 7→ g(a)]ρ = [y 7→ a]
2. Unifikator [x 7→ g(g(y)), z 7→ g(y)] ist allgemeiner als[x 7→ g(g(y)), z 7→ g(y), v 7→ g(b)]ρ = [v 7→ g(b)]
Allgemeinster Unifikator
Zu unifizierbaren Termen s, t existiert (bis auf Umbenennungder Variablen) genau ein Unifikator θ mit der folgendenEigenschaft:Für jeden Unifikator σ für s, t ist θ allgemeiner als σ.Dieser heißt allgemeinster Unifikator θ = mgu(s, t) von s und t .
(analog ggT)
Beispiele:I mgu(f (x ,a), f (g(b), y)) = [x 7→ g(b), y 7→ a]
I mgu(f (x ,g(y)), f (g(z), z)) = [x 7→ g(g(y)), z 7→ g(y)]
Unifizierbarkeit
I Jeder Term t ist mit t unifizierbar.allgemeinster Unifikator mgu(t , t) = []
I Jeder Term t ist mit jeder Variable x ∈ X, die nicht in tvorkommt, unifizierbar.allgemeinster Unifikator mgu(t , t) = [x 7→ t ]
I f (t1, . . . , tn) und g(s1, . . . , sm) sind nicht unifizierbar,falls f 6= g oder n 6= m
I θ ist Unifikator für f (t1, . . . , tn), f (s1, . . . , sn) gdw.∀i ∈ {1, . . . ,n} : θ unifiziert ti und si
Unifikation – Aufgabe
Eingabe: Terme s, t ∈ Term(Σ,X)
Ausgabe: ein allgemeinster Unifikator (mgu)σ : X→ Term(Σ,X) mit sσ = tσ.
Satz: Jedes Unifikationsproblem istI entweder gar nichtI oder bis auf Umbenennung eindeutig
lösbar.
Unifikation – Algorithmus
Berechnung von σ = mgu(s, t) für Terme s, t ∈ Term(Σ,X)durch Fallunterscheidung:
I s ∈ X:falls s 6∈ var(t), dann σ = [s 7→ t ],sonst nicht unifizierbar
I t ∈ X: symmetrischI s = f (s1, . . . , sm) und t = g(t1, . . . , tn):
falls f 6= g oder m 6= n, dann nicht unifizierbarsonst σ = mgu(s1, t1) ◦ · · · ◦mgu(sm, tm)
Dabei gilt für jede Substitution θ:
θ◦„nicht unifizierbar“ = „nicht unifizierbar“◦θ = „nicht unifizierbar“
Unifikationsalgorithmus – Beispiele
I mgu(f (x ,h(y), y), f (g(z), z,a)) =[x 7→ g(h(a)), z 7→ h(a), y 7→ a]
I mgu (k(f (x),g(y ,h(a, z))), k(f (g(a,b)),g(g(u, v),w))) =[x 7→ g(a,b), y 7→ g(u, v),w 7→ h(a, z)]
I mgu(k(f (a),g(x)), k(y , y)) existiert nichtI mgu(f (x ,g(a, z)), f (f (y), f (x)) existiert nichtI mgu(f (x , x), f (y ,g(y)) existiert nichtI mgu(f (x ,g(y)), f (y , x) existiert nicht
Unifikation von Haskell-Typen – Beispiele
I last :: [a] -> aTyp von [ 3, 5 .. 10 ] ist [Int]angewendete Instanz der Funktionlast :: [Int] -> Int ,
der Typ von last [ 3, 5 .. 10 ] ist also Int
I take :: Int -> [a] -> [a]Typ von take 1 ?Typ von take 1 [ "foo", "bar" ] ?
Was bisher geschahI Deklarative vs. imperative ProgrammierungI Funktionale Programmierung:
Programm: Menge von Gleichungen von Termen(Konstruktor-System)
Ausdruck hat Typ und Wert (zu berechnen)Ausführung: Pattern matching, Termersetzung
Funktionale Programmierung in HaskellI rekursive FunktionenI algebraische Datentypen und Pattern MatchingI rekursive Datentypen
(Peano-Zahlen)I strukturelle InduktionI Typen, Typ-Konstruktoren, Typ-SynonymeI PolymorphieI Typ-Inferenz, Unifikation
Datentyp Liste (polymorph)data List a = Nil
| Cons { head :: a, tail :: List a}
oder kürzer (vordefiniert)
data [a] = []| a : [a]
Pattern Matching:
f :: [a] -> ...f xs = case xs of
[] -> ...(x : xss) -> ...
Beispiel:
append :: [a] -> [a] -> [a]append xs ys = case xs of
[] -> ys(x : xss) -> x : (append xss ys)
Strukturelle Induktion über Listen
zum Nachweis von Eigenschaften wie z.B.I append xs [] = xs
I append ist assoziativ, d.h
append xs (append ys zs) = append (append xs ys) zs
Länge der Eingabeliste
len :: [a] -> Intlen xs = case xs of
[] -> 0(x : xss) -> 1 + len xss
Strukturelle Induktion zum Nachweis vonlen ( append xs ys ) = len xs + len ys
Mehr Beispiele
Summe aller Elemente der Eingabeliste
sum :: [Int] -> Intsum xs = case xs of
[] -> ...(x : xss) -> ...
jedes Element der Eingabeliste verdoppeln
doubles :: [Int] -> [Int]doubles xs = case xs of
[] -> []( y : ys ) -> ... : (doubles ys)
Strukturelle Induktion zum Nachweis vonsum ( doubles xs ) = 2 * ( sum xs )
Sortierte Listen(aufsteigend geordnet)
sortiert :: [Int] -> Boolsortiert xs = case xs of
[] -> True[ _ ] -> True(x : y : ys) -> x <= y && sortiert (y : ys)
sortiertes Einfügen:
insert :: Int -> [Int] -> [Int]insert y xs = case xs of
[] -> ...( x : xs ) -> if ...
then ...else ...
Strukturelle Induktion zum Nachweis von:Aus sortiert xs folgt sortiert ( insert x xs )
List Comprehensions – MotivationMenge der Quadrate aller geraden Zahlen zwischen 0 und 20:
{i2 | i ∈ {0, . . . ,20} ∧ i ≡ 0 (mod 2)}
Liste der Quadrate aller geraden Zahlen zwischen 0 und 20:(i2)
i∈[0,...,20],i≡0 (mod 2)
Definition der Menge / Liste enthält:Generator i ∈ [0, . . . ,20]
Funktion 2 : N→ NBedingung i ≡ 0 (mod 2)
als List Comprehension in Haskell:
[ i ^ 2 | i <- [0 .. 20], rem i 2 == 0]
List ComprehensionsI mit einem Generator[ f x | x <- ..]
z.B. [ 3 * x | x <- [1 .. 5] ]I mit mehreren Generatoren[ f x1 .. xn |x1 <- .., .. , xn <- .. ]
z.B.[ ( x , y ) | x <- [1 .. 3], y <- [0,1] ]
[ (x, x * y, x + z) | x <- [1 .. 5], y <- [0 .. 2], z <- [3 ..] ]
I mit Bedingungen:[ f x1 .. xn | x1 <- .., .. , xn <- ..
, r1 xi xj , .. , rk xi xj ]
z.B.[ ( x , y ) | x <- [1 .. 5], y <- [ 0 .. 4 ]
, x + y > 5, rem x 2 == 0]
Beispiele
[ ( x, y ) | x <- [ 1 .. 3 ], y <- [ 4 , 5 ] ]
[ ( x, y ) | y <- [ 1 .. 3 ], x <- [ 4 , 5 ] ]
[ x * y | x <- [ 1 .. 3 ], y <- [ 2 .. 4 ] ]
[ x * y | x <- [1 .. 3], y <- [2 .. 4], x < y ]
[ ’a’ | _ <- [1 .. 4] ]
[ [1 .. n] | n <- [0 .. 5] ]
hat welchen Typ?
[ x | xs <- xss , x <- xs ] ]
xss hat welchen (allgemeinsten) Typ?
Mehr Beispiele
teiler :: Int -> [ Int ]
teiler x = [ y | y <- [ 1 .. x ], rem x y == 0 ]
prim :: Int -> Bool
prim x = ( teiler x ) == [ 1, x ]
primzahlen :: [ Int ]
primzahlen = [ x | x <- [ 2 .. ], prim x ]
( später auch anders )
Was bisher geschah
I Deklarative vs. imperative ProgrammierungI Funktionale Programmierung:
Programm: Menge von Gleichungen von Termen(Konstruktor-System)
Ausdruck hat Typ und Wert (zu berechnen)Ausführung: Pattern matching, Termersetzung
Funktionale Programmierung in HaskellI rekursive FunktionenI algebraische Datentypen und Pattern MatchingI rekursive Datentypen
I Peano-Zahlen,I Listen
I strukturelle InduktionI Typen, Polymorphie, Typ-Inferenz
Datentyp Binärbaum (polymorph)data Bintree a = Leaf {}
| Branch { left :: Bintree a,key :: a,right :: Bintree a }
Beispiel:
t :: Bintree Intt = Branch {
left = Branch { left = Leaf {},key = 5,right = Leaf {} },
key = 3,right = Branch {
left = Leaf {},key = 2,right = Branch { left = Leaf {},
key = 4,right = Leaf {} }}}
Pattern Matching
data Bintree a = Leaf {}| Branch { left :: Bintree a,
key :: a,right :: Bintree a }
f :: Bintree a -> ..f t = case t of
Leaf {} -> ..Branch {} -> ..
oder tiefer:
f :: Bintree a -> ..f t = case t of
Leaf {} -> ..Branch { left = l, key = k, right = r } -> ..
Rekursion über binäre Bäume – BeispieleAnzahl der inneren Knoten
count :: Bintree a -> Intcount t = case t of
Leaf {} -> 0Branch {} -> count (left t)
+ 1 + count (right t)
Anzahl der Blätter:
leaves :: Bintree a -> Intleaves t = case t of
Leaf {} -> ...Branch {} -> ...
Summe der Schlüssel (Int):
bt_sum :: Bintree Int -> Intbt_sum t = case t of
Leaf {} -> ...Branch {} -> ...
Mehr Beispielejeden Schlüssel verdoppeln
doubles :: Bintree Int -> Bintree Intdoubles t = case t of
Leaf {} -> Leaf {}Branch {} -> ...
inorder :: Bintree a -> [a]inorder t = case t of
Leaf {} -> []Branch {} -> ...
vollständiger binärer Baum der Höhe h:
full :: Int -> Bintree Intfull h = if h > 0
then Branch { left = full (h-1),key = h,right = full (h-1) }
else Leaf {}
Strukturelle Induktion über Binärbäume
z.z. Jeder Binärbaum t mit Schlüsseln vom Typ ahat die Eigenschaft P
IA (t = Leaf): z.z.: Leaf hat die Eigenschaft PIS IV: Binärbäume l und r erfüllen P
IB: ∀ k :: a hat der BinärbaumBranch { left = l,
key = k,right = r }
die Eigenschaft P
zum Nachweis von Eigenschaften wie z.B.
I ∀ ( t :: Bintree Int ) :bt_sum (doubles t) = 2 * bt_sum t
I ∀ ( t :: Bintree Int ) :bt_sum t = list_sum ( inorder t )
Was bisher geschahI Deklarative vs. imperative ProgrammierungI Funktionale Programmierung:
Programm: Menge von Gleichungen von Termen(Konstruktor-System)
Ausdruck hat Typ und Wert (zu berechnen)Ausführung: Pattern matching, Termersetzung
Funktionale Programmierung in HaskellI rekursive FunktionenI algebraische Datentypen und Pattern MatchingI rekursive Datentypen
I Peano-Zahlen,I Listen,I Binärbäume
I strukturelle InduktionI Typen, Polymorphie, Typ-Inferenz
Wiederholung Datentyp Binärbaum (polymorph)data Bintree a = Leaf {}
| Branch { left :: Bintree a,key :: a,right :: Bintree a }
Beispiel:
t :: Bintree Intt = Branch {
left = Branch { left = Leaf {},key = 1,right = Leaf {} },
key = 3,right = Branch {
left = Leaf {},key = 4,right = Branch { left = Leaf {},
key = 6,right = Leaf {} }}}
Binäre SuchbäumeSuchbaum-Eigenschaft:Ein binärer Baum t :: Bintree Int ist genau dann einSuchbaum, wenn seine Knoten in Inorder-Durchquerung(aufsteigend) geordnet sind.
search_tree t = sortiert (inorder t)
mit
sortiert :: [ Int ] -> Boolsortiert [] = Truesortiert [ x ] = Truesortiert ( x : y : xs ) = ...
Einfügen eines Schlüssels in einen binären Suchbaum:
insert :: Int -> Bintree Int -> Bintree Intinsert x t = case t of
Leaf {} -> Branch { left = Leaf {},key = x,right = Leaf {} }
Branch {} -> ...
Sortieren durch Einfügen in binäre Suchbäume
Einfügen mehrerer Schlüssel in binären Suchbaum:
inserts :: [Int] -> Bintree Int -> Bintree Intinserts xs t = case xs of
[] -> t( x : xss ) -> ...
Sortieren durch Einfügen in binären Suchbaum:
sort :: [Int] -> [Int]sort xs = inorder ( inserts xs Leaf )
Strukturelle Induktion über Bäume
zum Nachweis von Eigenschaften wie z.B.
I bt_sum (insert x t) = x + bt_sum t
I Für jeden Suchbaum t ist inorder t sortiert.I Einfügen, Löschen eines Knotens erhalten die
Suchbaum-Eigenschaft.
Eingeschänkte Polymorphie
reverse [1,2,3,4] = [4,3,2,1]reverse "foobar" = "raboof"reverse :: [a] -> [a]
reverse ist polymorph
Sortieren von Listen
sort [5,1,4,3] = [1,3,4,5]sort "foobar" = "abfoor"
sort :: [a] -> [a] -- ??sort [sin,cos,log] = ??
sort ist eingeschränkt polymorph
Eingeschränkte Polymorphie in Haskell durch Typklassen
Beispiel Sortieren/VergleichenEinfügen (in monotone Liste)
insert :: Int -> [Int] -> [Int]insert x ys = case ys of[] -> [x]y : ys’ -> if x < y then .. else ..
Sortieren durch Einfügen:
sort :: [Int] -> [Int]sort xs = case xs of[] -> []x : xs’ -> insert x (sort xs’)
Einfügen/Sortieren für beliebige Typen:mit Vergleichsfunktion lt :: a -> a -> Boolals zusätzlichem Argument
insert :: ( a -> a -> Bool ) -> a -> [a] -> [a]insert lt x ys = ... if lt x y then ...
Sortieren/Vergleichen
Sortieren enthält Vergleiche <
Für alle Typen a, die für die es eine Vergleichs-Funktioncompare gibt, hat sort den Typ [a] -> [a].
sort :: Ord a => [a] -> [a]
Ord ist eine Typklasse, definiert durch
class Ord a wherecompare :: a -> a -> Ordering
data Ordering = LT | EQ | GT
InstanzenTypen können Instanzen von Typklassen sein.
(analog in OO: Klassen implementieren Interfaces)
Für vordefinierte Typen sind auch die meisten sinnvollenInstanzen vordefiniert
instance Ord Int ; instance Ord Char ; ...
weitere Instanzen kann man selbst deklarieren:
data Student = Student { vorname :: String, nachname :: String, matrikel :: Int}
instance Ord Student wherecompare s t =compare (matrikel s) (matrikel t)
Typen und Typklassen
In Haskell sind unabhängig:1. Deklaration einer Typklasse
(= Deklaration von abstrakten Methoden)class C where { m :: ... }
2. Deklaration eines Typs(= Sammlung von Konstruktoren und konkreten Methoden)data T = ...
3. Instanz-Deklaration(= Implementierung der abstrakten Methoden)instance C T where { m = ... }
In Java sind 2 und 3 nur gemeinsam möglichclass T implements C { ... }
Typen mit Gleichheit
class Eq a where(==) :: a -> a -> Bool(/=) :: a -> a -> Bool
Beispiele:I (’a’ == ’b’) = False
I (True /= False) = True
I ("ab" /= "ac") = True
I ([1,2] == [1,2,3]) = False
I (\ x -> 2 * x) == (\ x -> x + x) = ?
Typen mit totaler OrdnungInstanzen der Typklasse Eq mit
data Ordering = LT | EQ | GTclass Eq a => Ord a where
compare :: a -> a -> Ordering(<) :: a -> a -> Bool(<=) :: a -> a -> Bool(>) :: a -> a -> Bool(>=) :: a -> a -> Boolmin :: a -> a -> amax :: a -> a -> a
Beispiele:I (’a’ < ’b’) = True
I (False < True) = True
I ("ab" < "ac") = True (lexikographisch)I ([1,2] > [1,2,3]) = False
Klassen-Hierarchien
Typklassen können in Beziehung stehen.Ord ist „abgeleitet“ von Eq:
class Eq a where(==) :: a -> a -> Bool
class Eq a => Ord a where(<) :: a -> a -> Bool
Ord ist Typklasse mit Typconstraint (Eq)also muss man erst die Eq-Instanz deklarieren, dann dieOrd-Instanz.
Instanzen
data Bool = False | True
instance Eq Bool whereFalse == False = TrueTrue == True = True_ == _ = False
zu definieren:
instance Ord Bool whereFalse < True = True_ < _ = False
abgeleitet:
x <= y = ( x < y ) || ( x == y )x > y = y < xx >= y = y <= x
Typen mit Operation zum (zeilenweisen) Anzeigen
class Show a whereshow :: a -> String
Beispiele:I show 123 = "123"
I show True = "True"
I show [1,2] = "[1,2]"
I show (1,’a’,True) = "show (1,’a’,True)"
Instanzen Bool, Char, Int, Integer, Float,Listen und Tupel von Instanzen
Typklasse Show
Interpreter ghci gibt bei Eingabe exp (normalerweise)show exp aus.
Man sollte (u. a. deswegen) für jeden selbst deklariertenDatentyp eine Show-Instanz schreiben.
. . . oder schreiben lassen: deriving Show
Typen mit Operation zum Lesen
class Read a whereread :: String -> a
Beispiele:I ( read "3" :: Int ) = 3
I ( read "3" :: Float ) = 3.0
I ( read "False" :: Bool ) = False
I ( read "’a’" :: Char ) = ’a’
I ( read "[1,2,3]" :: [Int] ) = [1,2,3]
Instanzen Bool, Char, Int, Integer, Float,Listen und Tupel von Instanzen
Numerische Typen
class (Eq a, Show a) => Num a where(+) :: a -> a -> a(-) :: a -> a -> a(*) :: a -> a -> anegate :: a -> aabs :: a -> asignum :: a -> a
Beispiele:I signum (-3) = -1
I signum (-3.3) = -1.0
Instanzen Int, Integer, Float
Numerische Typen mit DivisionGanzzahl-Division:
class Num a => Integral a wherediv :: a -> a -> amod :: a -> a -> a
Instanzen Int, IntegerBeispiel: 3 ‘div‘ 2 = 1
Division:
class Num a => Fractional a where(/) :: a -> a -> arecip :: a -> a -> a
Instanzen: Float, Double
Beispiel: 3 / 2 = 0.6
Generische Instanzenclass Eq a where
(==) :: a -> a -> Bool
Vergleichen von Listen (elementweise)wenn a in Eq, dann [ a ] in Eq:
instance Eq a => Eq [a] where[] == [] = True
(x : xs) == (y : ys) = (x == y) && ( xs == ys )_ == _ = False
instance Ord a => Ord [a] wherecompare [] [] = EQcompare [] (_:_) = LTcompare (_:_) [] = GTcompare (x:xs) (y:ys) = case compare x y of
EQ -> compare xs ysother -> other
Abgeleitete InstanzenDeklaration eigener Typen als Instanzen von Standardklassendurch automatische Erzeugung der benötigten Methoden:
Beispiele:
data Bool = False | Truederiving (Eq, Ord, Show, Read)
data Shape = Circle Float | Rect Float Floatderiving (Eq, Ord, Show, Read)
z.B. (Circle 3 < Rect 1 2) == True
data Maybe a = Nothing | Just aderiving (Eq, Ord, Show, Read)
z.B. (Just ’a’ == Just ’b’) == False
Was bisher geschah
I Deklarative vs. imperative ProgrammierungI Deklarative Programmierung
Funktionale Programmierung in Haskell:I Algebraische DatentypenI Pattern MatchingI (eingeschränkte) Polymorphie, TypklassenI Rekursive Datentypen:
Peano-Zahlen, Listen, binäre BäumeI Rekursive FunktionenI strukturelle Induktion
Funktionen als Daten
bisher:
f :: Int -> Intf x = 2 * x + 5
äquivalent: Lambda-Ausdruck
f = \ x -> 2 * x + 5
Lambda-Kalkül: Alonzo Church 1936, Henk Barendregt 1984,. . .
Funktionsanwendung:
( \ x -> B ) A = B [ x := A ]
ist nur erlaubt, falls keine in A freie Variable durch ein λ in Bgebunden wird.
Der Lambda-Kalkül
. . . als weiteres Berechnungsmodell,(vgl. Termersetzungssysteme, Turingmaschine,Random-Access-Maschine)
Syntax (induktive Definition):Die Menge der Lambda-Terme Λ(X) mit Variablen aus X ist
IA: jede Variable ist ein Term: v ∈ X⇒ v ∈ Λ(X)
IS: Applikation , Funktionsanwendung:Für alle F ∈ Λ(X),A ∈ Λ(X) gilt (FA) ∈ Λ(X)
Abstraktion , Funktionsdefinition:Für alle v ∈ X,B ∈ Λ(X) gilt (λv .B) ∈ Λ(X)
Semantik: eine Relation→β auf Λ(X)(vgl.→R für Termersetzungssystem R)
Freie und gebundene Variablen(vorkommen)
I Das Vorkommen von v ∈ X an Position p in Term t ∈ Λ(X)heißt frei, wenn „darüber kein λv . . . . steht“
I Definition (durch strukturelle Induktion):fvar(t) = Menge der in t frei vorkommenden Variablen
I Eine Variable x heißt in A gebunden, falls A einenTeilausdruck λx .B enthält.
I bvar(t) = Menge der in t gebundenen Variablen
Beispiele:I fvar(x(λx .λy .x)) = {x},I bvar(x(λx .λy .x)) = {x , y}
Semantik des Lambda-Kalküls
Relation→β auf Λ(X) (ein Reduktionsschritt)
Es gilt t →β t ′, fallsI ∃p ∈ Pos(t), so daßI t [p] = (λx .B)A mit bvar(B) ∩ fvar(A) = ∅I t ′ = t [p := B[x := A]]
dabei bezeichnet B[x := A] ein Kopie von B, bei der jedesfreie Vorkommen von x durch A ersetzt ist
Ein (Teil-)Ausdruck der Form (λx .B)A heißt Redex.(Dort kann weitergerechnet werden.)
Ein Term ohne Redex heißt Normalform.(Normalformen sind Resultate von Rechnungen.)
Relation→α: gebundene Umbenennung
Lambda-Terme: verkürzte Notation
I Applikation als links-assoziativ auffassen, Klammernweglassen:
(. . . ((FA1)A2) . . .An) ∼ FA1A2 . . .An
Beispiel: ((xz)(yz)) ∼ xz(yz)
I geschachtelte Abstraktionen unter ein Lambda schreiben:
λx1.(λx2. . . . (λxn.B) . . . ) ∼ λx1x2 . . . xn.B
Beispiel: λx .λy .λz.B ∼ λxyz.B
Funktionen höherer OrdnungFunktionen als Argument von FunktionenBeispiel:
twice :: (a -> a) -> a -> atwice f x = f (f x)
Anwendung:I double hat den Typ Int -> Int
I twice double hat den Typ Int -> Int
I twice double 3hat den Typ Int und den Wert ?
I \x -> 2 * x + 1 hat den Typ Int -> Int
I twice (\x -> 2 * x + 1)hat den Typ Int -> Int
I twice (\x -> 2 * x + 1) 3hat den Typ Int und den Wert ?
I succ 0, twice succ 0, twice twice succ 0
I twice (^2) 3, twice twice (^2) 3
I Typ von twice twice ? Typ von twice twice twice ?
Funktionen höherer Ordnung – Beispiele
I punktweise Summe zweier Funktionen:fsum :: (a -> Int) -> (a -> Int) -> (a -> Int)fsum f g x = (f x) + (g x)fsum f g = \x -> (f x) + (g x)
Beispiele:I fsum (*2) (+1) 4,I fsum len head [ 2 .. 5 ]
I Komposition von Funktionen:(.) :: (a -> b) -> (b -> c) -> (a -> c)(f . g) x = f (g x)(f . g) = \ x -> f (g x)
Beispiele:I ( ( \ x -> x * 2 ) . len ) "foo"I suchbaum = sortiert . inorder
Was bisher geschah
I Deklarative vs. imperative Programmierung
Funktionale Programmierung in Haskell:I Algebraische DatentypenI Pattern MatchingI PolymorphieI TypklassenI Rekursive Datentypen:
Peano-Zahlen, Listen, BäumeI Rekursive FunktionenI strukturelle InduktionI Funktionen höherer Ordnung
(mit Funktionen als Argumenten)I λ-Kalkül, β-Reduktion
Wiederholung: rekursive DatentypenI Peano-Zahlendata Nat = Z
| S Nat
I Listendata List a = Nil {}
| Cons { head :: a, tail :: List a}
oder kürzerdata [a] = [] | a : [a]
I Binärbäumedata Tree a = Leaf {}
| Branch { left :: Tree a,key :: a,right :: Tree a}
oder kürzerdata Tree a = Leaf
| Branch ( Tree a ) a ( Tree a )
Wiederholung: Funktionen auf rekursiven DatentypenEntwurf rekursiver Funktionen auf rekursiven Datentypen:
1. Typdefinition2. Angabe aller Basis- und rekursiven Fälle3. Definition der Ergebnisse der Basisfälle4. Definition der Ergebnisse der rekursiven Fälle5. evtl. Typ verallgemeinern
Beispiel: Summe aller Schlüssel eines Baumes
data Tree a = Leaf| Branch (Tree a) a (Tree a)
1. Typdefinition: tsum :: Tree Int -> Int2. Angabe aller Basis- und rekursiven Fälle:
tsum t = case t ofLeaf -> ...Branch l k r -> ...
3. Definition der Ergebnisse der Basisfälle: Leaf -> 04. Definition der Ergebnisse der rekursiven Fälle:
Branch l k r -> (tsum l) + k + (tsum r)
Wiederholung: Funktionen auf Listen und Bäumen
Operationen auf Listen:I Verdoppeln jedes ListenelementsI Angabe gerade / ungerade für jedes ListenelementI Länge der ListeI Summe aller Listenelemente
Operationen auf Bäumen:I Verdoppeln jedes SchlüsselsI Angabe gerade / ungerade für jeden SchlüsselI Anzahl aller SchlüsselI Summe aller SchlüsselI Inorder-Durchquerung
Wiederholung: Funktionen auf ListenBeispiel: Verdoppeln jedes Elementes in einer Liste
double :: Int -> Intdouble x = x + xdoubles :: [Int] -> [Int]doubles xs = case xs of
[] -> [](y:ys) -> (double y) : (doubles ys)
oder mit anonymer Funktion (λ-Notation):
doubles :: [Int] -> [Int]doubles xs = case xs of
[] -> [](y:ys) -> ((\ x -> x + x) y) : (doubles ys)
evens :: [Int] -> [Bool]evens xs = case xs of
[] -> [](y:ys) -> ((\x->(mod x 2 == 0)) y) : (evens ys)
Rekursionsmuster für Listen
gemeinsame Eigenschaft:Ergebnis ist die Liste der Funktionswerte jedes Elementes derEingabeliste
I Parameter:I auf jedes Element anzuwendende Funktion h :: a -> bI Liste vom Typ [a]
I Ergebnis: Liste vom Typ [b]
I Berechnung (Pattern Matching):f xs = case xs of
[] -> [](x : xss) -> ( h x ) : ( f xss )
Rekursionsmuster mapBeschreibung des Rekursionsschemas
f x = case x of[] -> [](x : xss) -> ( h x ) : ( f xss )
durch eine Funktion höherer Ordnungmit der Funktion h :: a -> b als Argument
map :: ( a -> b ) -> [a] -> [b]
Anwendung: f = map h
ermöglicht kurze Funktionsdefinition, z.B.
doubles :: [ Int ] -> [ Int ]doubles = map double
oder mit anonymer Funktion: doubles = map (\z -> z*2)
oder noch kürzer: doubles = map ( *2 )
filterBeispiel: nur gerade Zahlen der Eingabeliste
ev :: Int -> Boolev = \x -> ( mod x 2 == 0 )
evens :: [Int] -> [Int]evens xs = case xs of
[] -> []( x : xss ) -> if ev x
then x : ( evens xss )else ( evens xss )
Funktion höherer Ordnung:
filter :: ( a -> Bool ) -> [a] -> [a]filter p xs = case xs of
[] -> []( x : xss ) -> if ( p x )
then x : ( filter p xss )else filter p xss
filter
ev :: Int -> Boolev = \x -> ( mod x 2 == 0 )
filter :: (a -> Bool) -> [a] -> [a]filter p xs = case xs of
[] -> []( x : xss ) -> if ( p x )
then x : ( filter p xss )else filter p xss
ermöglicht kurze Funktionsdefinitionen, z.B.:
evens = filter ev
oder mit anonymer Funktion
evens = filter ( \x -> ( mod x 2 == 0 ) )
filter ( < 100 ) ( map ( ^2 ) [ 0, 2 .. 10 ] )
Mehr rekursive Funktionen auf Listen
data [a] = [] | a : [a]
Länge einer Liste:
len :: [a] -> Intlen xs = case xs of
[] -> 0( _ : xss ) -> 1 + (len xss)
Summe aller Listenelemente:
sum :: [Int] -> Intsum xs = case xs of
[] -> 0( x : xss ) -> x + (sum xss)
Mehr Rekursionsmuster für Listen
gemeinsame Eigenschaft:
I Parameter:I Wert nil :: b für leere EingabelisteI Funktion cons :: a -> b -> b
zur Berechnung eines Wertes aus dem bisher berechnetenWert und einem Listenelement
I Liste vom Typ [a]
I Ergebnis vom Typ b
I Berechnung (Pattern Matching):f xs = case xs of
[] -> nil(x : xss) -> cons x ( f xss )
Rekursionschema fold
Funktion höherer Ordnung (mit Funktionen als Argumenten)
fold :: b -> (a -> b -> b) -> [a] -> bfold nil cons xs = case xs of
[] -> nilx : xss -> cons x ( fold nil cons xss )
ermöglicht kurze Funktionsdefinition, z.B.
len = fold 0 (\ x y -> 1 + x)sum = fold 0 (\ x y -> x + y)and = fold True (&&)
oder kurz: sum = fold 0 (+)
Funktionen höherer Ordnung für Listen
in Haskell vordefinierte Funktionen höherer Ordnung
I zur Verarbeitung von Listen:map :: (a -> b) -> [a] -> [b]foldr :: (a -> b -> b) -> b -> [a] -> bfilter :: (a -> Bool) -> [a] -> [a]takeWhile :: (a -> Bool) -> [a] -> [a]partition :: (a -> Bool) -> [a] -> ([a],[a])
I zum Vergleichen, Ordnen:nubBy :: (a -> a -> Bool) -> [a] -> [a]data Ordering = LT | EQ | GTminimumBy:: (a -> a -> Ordering) -> [a] -> a
Rekursionsschemata über Bäumedata Tree a = Leaf| Branch { left :: Tree a, key :: a, right :: Tree a }
doubles :: Tree Int -> [Int]doubles t = case t of
Leaf -> LeafBranch l k r -> Branch (doubles l) (k*2) (doubles r)
preorder :: Tree a -> [a]preorder t = case t of
Leaf -> []Branch l k r -> [ k ]
++ ( preorder l )++ ( preorder r )
sum :: Tree Int -> Intsum t = case t of
Leaf -> 0Branch l k r -> ( sum l ) + k + ( sum r )
Rekursionsschema map über Bäumef :: Tree a -> bf t = case t of
Leaf -> leafBranch l k r -> branch (f l) (g k) (f r)
Beispiel:
f = doublesg = double
Rekursionsschema:
tmap :: (a -> b ) -> ( Tree a ) -> ( Tree b )tmap f t = case t of
Leaf -> LeafBranch l k r -> Branch (tmap f l)
(f k)(tmap f r)
doubles = tmap ( 2* )
Rekursionsschema fold über Bäume
f :: Tree a -> bf t = case t of
Leaf -> leafBranch l k r -> branch (f l) k (f r)
Beispiel:
f = preorderleaf = []branch l’ k r’ = [k] ++ l’ ++ r’
Rekursionsschema:
tfold :: b -> (b -> a -> b -> b) -> (Tree a) -> btfold leaf branch t = case t of
Leaf -> leafBranch l k r -> branch (tfold leaf branch l)
k(tfold leaf branch r)
Beispiele: fold über Bäume
tfold :: b -> (b -> a -> b -> b) -> (Tree a) -> btfold leaf branch t = case t of
Leaf -> leafBranch l k r -> branch (tfold leaf branch l)
k(tfold leaf branch r)
preorder = tfold [] ( \ l’ k r’ -> [k] ++ l’ ++ r’ )sum = tfold 0 ( \ l’ k r’ -> l’ + k + r’ )
analog: Anzahl der Blätter, inneren Knoten, Tiefe
Rekursionsmuster (Merksätze)
Rekursionsmuster anwenden =jeden Konstruktor durch eine passende Funktion ersetzen
I Anzahl der Muster-Argumente =Anzahl der Konstruktoren(plus eins für das Datenargument)
I Stelligkeit eines Muster-Argumentes =Stelligkeit des entsprechenden Konstruktors
I Rekursion im Typ→ Rekursion im MusterI zu jedem rekursiven Datentyp gibt es genau ein
passendes Rekursionsmuster
Was bisher geschahFunktionale Programmierung in Haskell:
I Algebraische DatentypenI Pattern MatchingI PolymorphieI TypklassenI Rekursive Datentypen: Peano-Zahlen, Listen, BäumeI Rekursive FunktionenI strukturelle InduktionI Rekursionsschemata für Peano-Zahlen, Listen, BäumeI Funktionen höherer Ordnung
(mit Funktionen als Argumenten)I λ-Kalkül, β-ReduktionI fold auf rekursiven Datentypen
(Peano-Zahlen, Listen, Bäume)I map auf Listen und Bäumen, filter auf Listen
Nützliche Funktionentake :: Int -> [ a ] -> [ a ]take 0 _ = []take _ [] = []take n ( x : xs ) = x : ( take ( n - 1 ) xs )
take 3 [ 1 .. 10 ]
takeWhile :: ( a -> Bool ) -> [ a ] -> [ a ]takeWhile p xs = case xs of
[] -> []x : xss -> if p x
then x : ( take While p xss )else []
takeWhile ( \ x -> mod x 5 < 4) [ 1 .. 10 ]
dropWhile :: ( a -> Bool ) -> [ a ] -> [ a ]dropWhile p xs = case xs of
[] -> []x : xss -> if p x
then ( dropWhile p xss )else xss
dropWhile ( < 4 ) [ 1 .. 10 ]
Nützliche Funktionen
zip :: [ a ] -> [ b ] -> [ ( a , b ) ]zip ( x : xs ) ( y : ys )
= ( x, y ) : zip ( xs ) ( ys )zip _ _ = []
zip "foo" [1 .. 5]
zipWith :: ( a -> b -> c )-> [ a ] -> [ b ] -> [ c ]
zipWith f xs ys = map ( \ ( x, y ) -> f x y )( zip xs ys )
zipWith (+) [ 1 .. 10 ] [ 2, 4 .. 10 ]
zipWith (\x y -> ( foldr (\ _ y -> 1 + y) 0 x) + y)[ "foo", "b", "ar" ] [ 1 .. 10 ]
Wiederholung – Auswertung von Ausdrücken
Reduktion: Termersetzung durch FunktionsanwendungRedex: reduzierbarer Teilterm
Normalform: nicht-reduzierbarer Ausdruck(Ausdruck ohne Redex)
Auswertung: schrittweise Reduktion, bis Normalform erreicht
square :: Int -> Intsquare x = x * x
2 Möglichkeiten,den Wert von square (3 + 1) zu berechnen
Es wird bei beiden Möglichkeiten derselbe Wert berechnet.(Haskell ist nebenwirkungsfrei.)
Auswertungsreihenfolge
mult :: Int -> Int -> Intmult = \x y -> x * y
Redexe von
mult ( 1 + 2 ) ( 2 + 3 )
data N = Z | S Nnichtnull :: N -> Boolnichtnull n = case n of
Z -> FalseS _ -> True
Redexe von
nichtnull ( S undefined )
Auswertungs-Strategieninnermost Reduktion von Redexen, die keinen Redex
enthalten(Parameterübergabe by value)
outermost Reduktion von Redexen, die in keinem Redexenthalten sind(Parameterübergabe by name)
(jeweils so weit links wie möglich zuerst)
square :: Int -> Intsquare x = x * x
square (3 + 1)
Teilterme in λ-Ausdrücken werden nicht reduziert.
(\ x -> 1 + 2) 1
Termination
inf :: Intinf = 1 + inf
fst :: ( a , b ) -> afst ( x, y ) = x
Auswertung von
fst (3, inf)
terminiert unter outermost-Strategie,aber nicht unter innermost-Strategie
SatzFür jeden Ausdruck, für den die Auswertung unter irgendeinerStrategie terminiert, terminert auch die Auswertung unteroutermost-Strategie.
Unendliche Datenstrukturen
nats_from :: Int -> [ Int ]nats_from n = n : ( nats_from ( n + 1 ) )
nats_from 3
outermost-Auswertung von
head ( tail ( tail ( nats_from 3 ) )
= head ( tail ( tail ( 3 : ( nats_from ( 3 + 1 )))))= head ( tail ( nats_from (3 + 1)))= head ( tail ( (3 + 1) : nats_from (( 3 + 1 ) + 1 ))= head ( nats_from ( ( 3 + 1 ) + 1 ) )= head (((3 + 1) + 1) : nats_from (((3 + 1) + 1) + 1))= ( 3 + 1 ) + 1= 4 + 1= 5
Lazyness
I jeder Wert wird erst bei Bedarf ausgewertet.I Listen sind Streams, der Tail wird erst bei Bedarf
ausgewertet.I Wann die Auswertung stattfindet, lässt sich nicht
beobachten.
Die Auswertung hat keine Nebenwirkungen.
Strictness
zu jedem Typ T betrachte T⊥ = {⊥} ∪ T
dabei ist ⊥ ein „Nicht-Resultat vom Typ T “I Exception undefined :: T
I oder Nicht-Termination let { x = x } in x
Definition:Funktion f heißt strikt, wenn f (⊥) = ⊥.Funktion f mit n Argumenten heißt strikt in i , falls(xi = ⊥)⇒ f (x1, . . . , xn) = ⊥
in Haskell:I Konstruktoren (Cons,. . . ) sind nicht strikt,I Destruktoren (head, tail,. . . ) sind strikt.
Strictness – Beispiele
I length :: [a] -> Int ist strikt:length undefined ==> exception
I (:) :: a->[a]->[a] ist nicht strikt im 1. Argument:
length (undefined : [2,3]) ==> 3
d.h. (undefined : [2,3]) ist nicht ⊥I (&&) ist strikt im 1. Argument,
nicht strikt im 2. Argumentundefined && True ==> (exception)False && undefined ==> False
Lazy Evaluation – Realisierung
Begriffe:nicht strikt : nicht zu früh auswerten
lazy : höchstens einmal auswerten
bei jedem Konstruktor- und Funktionsaufruf:I kehrt sofort zurückI Resultat ist thunkI thunk wird erst bei Bedarf ausgewertetI Bedarf entsteht durch Pattern MatchingI nach Auswertung: thunk durch Resultat überschreiben
Lazy Evaluation (Bedarfsauswertung) =Outermost-Reduktionsstrategie mit Sharing
Unendliche Datenstruktureninf :: Intinf = 1 + inf
fst(3, inf)
einsen :: [Int]einsen = 1 : einsen
head einsentake 3 einsen
walzer :: [Int]walzer = 1 : 2 : 3 : walzer
nats :: [Int]nats = 0 : map (+1) nats
takeWhile (<= 5) nats
Liste aller Quadratzahlen? Primzahlen?
Motivation: Datenströme
Folge von Daten:I erzeugen (producer)I transformierenI verarbeiten (consumer)
aus softwaretechnischen Gründen:diese drei Aspekte im Programmtext trennen,
aus Effizienzgründen:in der Ausführung verschränken
(bedarfsgesteuerte Transformation/Erzeugung)
Rekursive Stream-Definitionennats = 0 : map (+1) nats
fibonacci = 0: 1: zipWith (+) fibonacci ( tail fibonacci )
take 10 fibonaccitake 1 $ dropWhile (< 200) fibonacci
Welchen Wert hat bin ?
bin = False: True: concat ( map ( \ x -> [ x, not x ] )
( tail bin ) )
Thue-Morse-Folge t = 0110100110010110 . . .mit vielen interessanten Eigenschaften, z.B.
I t := limn→∞ τn(0) für τ : 0 7→ 01,1 7→ 10I t ist kubikfreiI Abstandsfolge v := 210201210120 . . .
ist auch Fixpunkt eines Morphismus, quadratfrei
Primzahlen
Sieb des Eratosthenes
nats_from :: Int -> [ Int ]nats_from n = n : nats_from ( n + 1 )
primzahlen :: [ Int ]primzahlen = sieb $ nats_from 2
sieb :: [ Int ] -> [ Int ]sieb (x : xs) = ...
take 100 primzahlentakeWhile (< 100) primzahlen
Was bisher geschahFunktionale Programmierung in Haskell:
I Algebraische DatentypenI Pattern MatchingI PolymorphieI TypklassenI Rekursive Datentypen: Peano-Zahlen, Listen, BäumeI Rekursive FunktionenI strukturelle InduktionI Rekursionsschemata für Peano-Zahlen, Listen, BäumeI Funktionen höherer Ordnung
(mit Funktionen als Argumenten)I λ-Kalkül, β-ReduktionI fold auf rekursiven Datentypen
(Peano-Zahlen, Listen, Bäume)I map auf Listen und Bäumen, filter auf ListenI Bedarfsauswertung (lazy evaluation):
leftmost outermost reduction + sharing
Sortieren
sortiert :: Ord a => [a] -> Boolsortiert xs = foldr (&&) True
$ zipWith (<=) xs $ tail xs
sort :: Ord a => [a] -> [a]
z.B. durchI Einfügen in (anfangs leeren) binären SuchbaumI Inorder-Ausgabe
Klassische Sortier-Verfahren
I Sortieren durch Einfügeninsert :: Ord a => a -> [ a ] -> [ a ]insert x [] = [x]insert x ( y : ys ) | x <= y = x : y : ys
| x > y = y : (insert x ys)
isort :: Ord a => [a] -> [a]isort [] = []isort (x:xs) = insert x $ isort xs
I Quicksortqsort :: Ord a => [a] -> [a]qsort [] = []qsort (x:xs) = qsort [ y | y <- xs, y <= x]
++ [x] ++ qsort [ y | y <- xs, y > x]
Mergesort
merge :: Ord a => [a] -> [a] -> [a]merge xs [] = xsmerge [] ys = ysmerge (x : xs) (y : ys)
| x <= y = x : merge xs ( y : ys )| otherwise = y : merge ( x : xs ) ys
msort :: Ord a => [a] -> [a]msort [] = []msort [ x ] = [ x ]msort xs = merge ( msort l ) ( msort r )where ( l , r ) = splitAt halb xs
halb = div (length xs) 2
Ver- und EntschlüsselnVerschiebe-Chiffre
I symmetrisches Verschlüsselungs-Verfahren:derselbe Schlüssel zum Ver- und Entschlüsseln
I Substitutionschiffre: Ersetzung jedes Klartextsymbolesdurch ein Chiffretextsymbol
I monoalphabetisch: Klartextsymbol überall durch dasselbeChiffretextsymbol ersetzt
Klartextmenge: M = {a,b, . . . , z}∗
Chiffretextmenge: C = {a,b, . . . , z}∗
Schlüsselmenge: K = {0, . . . ,25}Verschlüsselung: jeden Buchstaben durch Buchstaben k
Positionen später im Alphabet ersetzenEntschlüsselung: jeden Buchstaben durch Buchstaben k
Positionen früher im Alphabet ersetzen
klassisches Beispiel: Caesar-Chiffre k = 3
Verschlüsselnfür jeden (Klein-)Buchstaben im Klartext:
I Buchstabe durch Zahl ∈ {0, . . . ,25} ersetzen
b2int :: Char -> Intb2int b = ord b - ord ’a’
I Zahl durch entsprechenden Buchstaben ersetzen
int2b :: Int -> Charint2b n = chr (ord ’a’ + n)
I Buchstabe mit Schlüssel k verschlüsseln:
enc :: Int -> Char -> Charenc k b | isLower b = int2b ( mod (k + b2int b) 26)
| otherwise = b
Klartext verschlüsseln:
encode :: Int -> String -> Stringencode k = map ( enc k )
Chiffretext entschlüsseln: . . .
Angriffe auf Verschiebechiffren
Ciphertext-Only-Angriffe auf Verschiebechiffren
gegeben: verschlüsselter TextI hinreichend lang,I natürlichsprachig (deutsch),I mit Verschiebechiffre verschlüsselt
gesucht: Klartext ( und evtl. Schlüssel )
Ideen für Angriffe:I Brute Force: Ausprobieren aller 26 SchlüsselI typische Häufigkeiten von Buchstaben,
Buchstabengruppen
Funktionen auf Listen / Strings
Anzahl der Vorkommen eines Elementes in einer Liste:
countEl :: Eq a => a -> [ a ] -> IntcountEl b = ( foldr (\x y -> y + 1) 0 )
. filter ( == b )
z.B. countEl ’o’ "foo" = 2
Anzahl der Kleinbuchstaben in einer Zeichenkette:
lowers :. String -> Intlowers = ( foldr (\x y -> y + 1) 0 )
. filter ( isLower )
z.B. lowers "Foo !" = 2
Funktionen auf Listen / Strings
alle Positionen eines Elementes in einer Liste:
positions :: Eq a => a -> [ a ] -> [ Int ]positions x xs = ( map fst )
$ filter (\ ( _ , y) -> y == x )$ zip [ 0 .. ] xs
z.B. positions ’o’ "foo" = [1,2]
Rotieren von Listen
rotate :: Int -> [ a ] -> [ a ]rotate n xs = drop n xs ++ take n xs
Buchstaben-HäufigkeitenHäufigkeiten (in deutschen Texten):
haeufigkeitstabelle :: [ Float ]haeufigkeitstabelle = [6.51, 1.89, 3.06, 5.08,17.4, 1.66, 3.01, 4.76, 7.55, 0.27, 1.21,3.44, 2.53, 9.78, 2.51, 0.79, 0.02, 7.00,7.27, 6.15, 4.35, 0.67, 1.89, 0.03, 0.04,1.13]
zip [’a’ .. ’z’] häufigkeitstabelle
proz :: Int -> Int -> Floatproz m n = (fromIntegral m / fromIntegral n) * 100
Prozentuale Häufigkeit im (verschlüsselten) Text:
häufigkeiten :: String -> [ Float ]häufigkeiten t = [ proz ( countEl x t ) n |
x <- [ ’a’ .. ’z’ ] ]where n = lowers t
StatistikTest auf (annähernd) gleiche Verteilung durchChi-Quadrat-Test für Buchstabenhäufigkeiten
I erwartet: e ∈ R{0,...,25}≥0
(häufigkeitstabelle)I im Text t aufgetreten: a ∈ R{0,...,25}
≥0(häufigkeiten t)
∀e,a ∈ R{0,...,25}≥0 : χ2(a,e) =
n−1∑i=0
(ai − ei)2
ei
chiquad :: [ Float ] -> [ Float ] -> Floatchiquad a e = foldr (\x y -> x + y) 0
$ zipWith (\ x y -> (x - y)^2 / y) a e
chiquad (häufigkeiten "ipiqirx") häufigkeitstabelle
Knacken der VerschiebechiffreChi-Test für alle möglichen Schlüssel k ∈ {0, . . . ,25} für Chiffretext c:
chitab = [ chiquad ( rotate k ( häufigkeiten c ) )häufigkeitstabelle| k <- [ 0 .. 25 ] ]
Index (Verschiebung) des kleinsten χ2-Wertes:
k = head ( positions (minimum chitab ) chitab )where chitab = [ chiquad (rotate n (häufigkeiten c))
häufigkeitstabelle| n <- [ 0 .. 25 ] ]
ist (wahrscheinlich) der Schlüssel
crack :: String -> Stringcrack c = decode k c
where k = head ( positions (minimum chitab ) chitab )chitab = [ chiquad (rotate n (häufigkeiten c))
häufigkeitstabelle| n <- [0 .. 25]]