Deklarative Programmierung - Startseiteschwarz/lehre/ss15/dp/fop15-folien.pdf · Funktionale...

Post on 17-Oct-2020

3 views 0 download

Transcript of Deklarative Programmierung - Startseiteschwarz/lehre/ss15/dp/fop15-folien.pdf · Funktionale...

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/~schwarzsibylle.schwarz@htwk-leipzig.de

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]]