Deklarative Programmierung

Werbung
Was bisher geschah
I
deklarative Programmierung
I
I
I
funktional:
Programm: Menge von Termgleichungen, Term
Auswertung: Pattern matsching, Termumformungen
logisch:
Programm: Menge von Regeln (Horn-Formeln), Formel
(Query)
Auswertung: Unifikation, Resolution
funktionale Programmierung (Haskell):
I
I
I
nebenwirkungsfrei
lazy evaluation (ermöglicht unendliche Datentypen)
kompakte Darstellung
15
Haskell-Programme
Semantik: Funktion von Eingabe auf Ausgabe
I
keine Variablen, also keine Programmzustände
(kein Aufruf-Kontext)
I
Wert jeder Funktion(sanwendung) hängt ausschließlich
von den Werten der Argumente ab
Syntax:
I
Ausdrücke: Terme
z.B. 2 + x * 7 oder double 2
I
Funktionsdefinition: Gleichung zwischen zwei Ausdrücken
z.B. inc x = x + 1
Programm:
I
I
I
Liste von Funktionsdefinitionen
Ausdruck
16
Ausdrücke
Ausdruck = Term (Baumstruktur)
Jeder Ausdruck hat
I
einen Typ und
I
einen Wert
Berechnung des Wertes durch schrittweise Reduktion
(Termersetzung)
17
Beispiele
Ausdruck 7 hat
I
den Typ Int
I
den Wert 7
Ausdruck 3 * 7 + 2 hat
I den Typ Int
I
den Wert ?
Reduktion: rekursive Berechnung des Wertes
18
Funktionsdeklarationen
double :: Int -> Int
double x = x + x
(Typdeklaration)
(Funktionsdefinition)
Ausdruck double 3 hat
I
den Typ Int
I
den Wert 6
Ausdruck double (double 3) hat
I
den Typ Int
I
den Wert ?
Ausdruck double hat
I
den Typ Int -> Int
I
den Wert x 7→ x + x (mathematische Notation)
λx.(x + x) (λ-Kalkül)
19
Typinferenz
Inferenzregel:
f :: A → B e :: A
f e :: B
(man bemerke die Analogie zum Modus Ponens)
Beispiel:
True :: Bool
False :: Bool
neg :: Bool -> Bool
neg b = case b of
True -> False
False -> True
Typ von neg True, neg (neg True)
sum [1,2,3]
20
Auswertung
Normalform: nicht-reduzierbarer Ausdruck
Auswertung: schrittweise Reduktion, bis Normalform erreicht
Auswertungsstrategien:
innermost-Reduktion (strikt)
outermost-Reduktion (lazy)
Beispiel:
double (double 3)
Besonders in Haskell:
I
Termination
I
Auswertungsreihenfolge egal (Konfluenz)
21
Definition von Funktionen
I
Fallunterscheidung
I
Rekursion
Beispiel:
fac n = if n < 1
then 1
else n * (fac (n-1))
zum Vergleich: Ablaufsteuerung in imperativen Sprachen
I
Nacheinanderausführung
I
Verzweigung (Fallunterscheidung)
I
Wiederholung (Iteration)
22
Funktionen als Daten
f :: Int -> Int
f x = 2 * x + 3
äquivalent: Lambda-Ausdruck
f = λx.(2x + 3)
Funktionsanwendung (Reduktion):
f
= λx.A
fB = A[x 7→ B]
falls x nicht (frei) in B vorkommt
Lambda-Kalkül: Alonzo Church 1936, Henk Barendregt 1984,
...
Beispiel: A = 2x + 3, B = 1
f
= λx.A = λx.(2x + 3)
fB = (λx.A)B = λx.(2x + 3)1
= 2·1+3=5
23
Rekursion und Pattern Matching
fac :: Int -> Int
fac 1 = 1
fac n = n * (fac (n-1))
Wert von fac 4 ?
Wert von fac (-4) ?
Verbesserungsvorschlag ?
alternative Darstellung:
fac :: Int -> Int
fac n = if (n > 1)
then n * (fac (n-1))
else 1
noch viel mehr:
http://www.willamette.edu/~fruehr/haskell/
evolution.html
24
Haskell-Datentypen
einfache Datentypen, z.B.
Int
ganze Zahlen (feste Länge)
Integer ganze Zahlen (beliebige Länge)
Bool
Wahrheitswerte
Char
ASCII-Symbole
Konstruktion zusammengesetzter Datentypen:
I
Produkt, z.B. Tupel
I
Summe (Fallunterscheidung)
z.B. Aufzählungstypen True, False
I
Rekursion, z.B. Listen, Bäume
I
Potenz, Funktionen
25
Algebraische Datentypen
data Foo = Foo { bar :: Int, baz :: String }
deriving Show
Bezeichnungen:
I
data Foo ist Typname
I
Foo { .. } ist Konstruktor
I
bar, baz sind Komponenten
x :: Foo
x = Foo { bar = 3, baz = "hal" }
Mathematisch: Produkt
Foo = Int × String
26
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) Summe
Bool = { False } ∪ { True }
27
Fallunterscheidung, Pattern Matching
data T = A { foo :: Int }
| B { bar :: String }
Fallunterscheidung:
f :: T -> Int
f x = case x of
A {} -> foo x
B {} -> length $ bar x
Pattern Matching (Bezeichner f,b werden lokal gebunden):
f :: T -> Int
f x = case x of
A { foo = f } -> f
B { bar = b } -> length b
28
Rekursive Datentypen: Peano-Zahlen
data Nat = Zero
| S Nat
Addition
add :: Nat -> Nat -> Nat
add x Zero = x
add x ( S y ) = S ( add x y )
Beispiel:
add (S (S (S Zero ))) (S (S Zero)) =
S (S (S (S (S Zero))))
Ausführung der Berechnungsschritte (Tafel)
Nat ist mit dieser Addition
assoziativ, kommutativ, add Zero x = x
Nachweis durch strukturelle Induktion (Tafel)
Definition weiterer Operationen: Multiplikation, Potenz
29
Datentyp Liste (polymorph)
eigentlich:
data List a = Nil {}
| Cons { head :: a, tail :: List a }
aber aus historischen Gründen
data [a] = [] | a : [a]
Pattern matching dafür:
len :: [a] ->
len xs = case
[]
->
(x : xss)
Int
xs of
0
-> ...
Summe der Elemente einer Liste?
30
Strukturelle Induktion über Listen
app :: [a] -> [a] -> [a]
app xs ys = case xs of
[]
-> ys
(x : xss) -> x : (app xss ys)
Strukturelle Induktion zum Nachweis von Eigenschaften wie
z.B.
I
app xs [] = xs
I
app ist assoziativ, d.h
append xs (app ys zs) = app (app xs ys) zs
31
Mehr Beispiele
Länge der Eingabeliste
len :: [a] ->
len xs = case
[]
(x : xss)
Int
xs of
-> 0
-> 1 + len xss
jedes Element der Eingabeliste verdoppeln
doubles
doubles
[]
( y
:: [Int] -> [Int]
xs = case xs of
-> []
: ys ) -> ( y + y ) : (doubles ys)
is_monoton :: [Int] -> Bool
is_monoton xs = case xs of
[] -> True
[ _ ] -> True
(x : y : ys) -> x <= y && is_monoton (y : ys)
32
Datentyp Binärbaum (polymorph)
data Tree a = Leaf {}
| Branch { left :: Tree a,
key
:: a,
right :: Tree a }
Pattern Matching:
f :: Tree a -> ..
f t = case t of
Leaf {} -> ..
Branch { left = l, key = k, right = r } -> ..
33
Rekursion über binäre Bäume
Anzahl der inneren Knoten
count :: Tree Int -> Int
count t = case t of
Leaf {}
-> 0
Branch {} -> count (left t)
+ 1 + count (right t)
Anzahl der Blätter:
leaves :: Tree a -> Int
leaves t = case t of
Leaf
{} -> ...
Branch {} -> ...
34
Mehr Beispiele
jeden Schlüssel verdoppeln
doubles :: Tree Int -> Tree Int
doubles t = case t of
Leaf {}
-> Leaf {}
Branch {} -> ...
inorder :: Tree a -> [a]
inorder t = case t of
Leaf {} -> []
Branch {} -> ...
vollständiger binärer Baum der Höhe h:
full :: Int -> Tree Int
full h = if h > 0
then Branch { left = full (h-1),
key = h,
right = full (h-1) }
else Leaf {}
35
Binäre Suchbäume
Suchbaum-Eigenschaft:
Ein binärer Baum t :: Tree Int ist genau dann ein
Suchbaum, wenn seine Knoten in Inorder-Durchquerung
aufsteigend geordnet sind.
search_tree t = is_monoton (inorder t)
Einfügen eines Schlüssels in einen binären Suchbaum:
insert :: Int -> Tree Int -> Tree Int
insert x t = case t of
Leaf {} -> Branch { left = Leaf {},
key = t,
right = Leaf {} }
Branch {} -> ...
36
Sortieren durch Einfügen in binäre Suchbäume
Einfügen mehrerer Schlüssel in binären Suchbaum:
inserts :: [Int] -> Tree Int -> Tree Int
inserts 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 )
37
Strukturelle Induktion über Bäume
zum Nachweis von Eigenschaften wie z.B.
I
Einfügen eines Knotens erhält die Suchbaum-Eigenschaft.
38
Herunterladen