Deklarative Programmierung

Werbung
Was bisher geschah
I
Deklarative vs. imperative Programmierung
I
Deklarative Programmierung: FP, LP, FLP, CLP
Haskell:
I
Algebraische Datentypen
I
Pattern Matching
I
Polymorphie
I
Rekursive Datentypen
(Peano-Zahlen, Listen, binäre Bäume)
I
Rekursive Funktionen
I
strukturelle Induktion
I
Funktionen höherer Ordnung
prominente Beispiele: filter, map, fold
(über Peano-Zahlen, Listen, binäre Bäume)
59
Wiederholung Typen
Beispiele:
I
last :: [a] -> a
Typ von [ 3, 5 .. 10 ] ist [Int]
angewendete Instanz der Funktion
last :: [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" ] ?
60
Polymorphie
nicht polymorphe Typen:
tatsächlicher Argumenttyp muss mit deklariertem Argumenttyp
übereinstimmen:
Wenn f :: A -> B und e :: A, dann ( f e ) :: B.
polymorphe Typen:
Typ von f :: A -> B und Typ von x :: A’ können
Typvariablen enthalten.
A und A’ müssen unfizierbar (eine gemeinsame Instanz
besitzen) aber nicht notwendig gleich sein.
σ = mgu(A, A0 ) (allgemeinster Unifikator)
allgemeinster Typ von ( f x ) ist dann σ(B)
Typ von x wird dadurch spezialisiert auf σ(A0 )
61
Eingeschänkte Polymorphie
reverse [1,2,3,4] = [4,3,2,1]
reverse "foobar" = "raboof"
reverse :: [a] -> [a]
reverse ist polymorph
sort [5,1,4,3] = [1,3,4,5]
sort "foobar" = "abfoor"
sort :: [a] -> [a] -- ??
sort [sin,cos,log] = ??
sort ist eingeschränkt polymorph
62
Motivation
sort enthält:
let ( low, high ) = partition ( < ) xs in ...
Für alle Typen a, die für die es eine Vergleichs-Funktion
compare gibt, hat sort den Typ [a] -> [a].
sort :: Ord a => [a] -> [a]
Ord ist eine Typklasse, definiert durch
class Ord a where
compare :: a -> a -> Ordering
data Ordering = LT | EQ | GT
63
Instanzen
Typen können Instanzen von Typklassen sein.
(analog in OO: Klassen implementieren Interfaces)
Für vordefinierte Typen sind auch die meisten sinnvollen
Instanzen 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 where
compare s t =
compare (matrikel s) (matrikel t)
64
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öglich
class T implements C { ... }
65
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) = ?
66
Typen mit totaler Ordnung
Instanzen der Typklasse Eq mit
data Ordering = LT | EQ | GT
class Eq a => Ord a where
compare :: a -> a -> Ordering
(<)
:: a -> a -> Bool
(<=)
:: a -> a -> Bool
(>)
:: a -> a -> Bool
(>=)
:: a -> a -> Bool
min
:: a -> a -> a
max
:: a -> a -> a
Beispiele:
I
(’a’ < ’b’) = True
I
(False < True) = True
I
("ab" < "ac") = True (lexikographisch)
I
([1,2] > [1,2,3]) = False
67
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 muß man erst die Eq-Instanz deklarieren, dann die
Ord-Instanz.
Jedes Ord-Wörterbuch hat ein Eq-Wörterbuch.
68
Instanzen
data Bool = False | True
instance Ord
False ==
True ==
_
==
Bool where
False = True
True
= True
_
= False
instance Eq Bool where
False < True = True
_
< _
= False
x
<= y
= ( x < y ) || ( x == y )
x
> y
= y < x
x
>= y
= y <= x
69
Typen mit Operation zum (zeilenweisen) Anzeigen
class Show a where
show :: 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
70
Typklasse Show
Die Interpreter Ghci / Hugs geben bei Eingabe exp
(normalerweise) show exp aus.
Man sollte (u. a. deswegen) für jeden selbst deklarierten
Datentyp eine Show-Instanz schreiben.
. . . oder schreiben lassen: deriving Show
71
Typen mit Operation zum Lesen
class Read a where
read :: 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
72
Numerische Typen
class (Eq a, Show a) => Num a where
(+)
:: a -> a -> a
(-)
:: a -> a -> a
(*)
:: a -> a -> a
negate :: a -> a
abs
:: a -> a
signum :: a -> a
Beispiele:
I
signum (-3) = -1
I
signum (-3.3) = -1.0
Instanzen Int, Integer, Float
73
Numerische Typen mit Division
Ganzzahl-Division:
class Num a => Integral a where
div
:: a -> a -> a
mod
:: a -> a -> a
Instanzen Int, Integer
Beispiel: 3 ‘div‘ 2 = 1
Division:
class Num a => Fractional a where
(/)
:: a -> a -> a
recip
:: a -> a -> a
Instanz Float
Beispiel: 3 / 2 = 0.6
74
Generische Instanzen
class 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
75
Abgeleitete Instanzen
Deklaration eigener Typen als Instanzen von Standardklassen
durch automatische Erzeugung der benötigten Methoden:
Beispiele:
data Bool = False | True
deriving (Eq, Ord, Show, Read)
data Shape = Circle Float | Rect Float Float
deriving (Eq, Ord, Show, Read)
Beispiel: (Circle 3 < Rect 1 2) == True
data (Eq a) => Maybe a = Nothing | Just a
deriving (Eq, Ord, Show, Read)
Beispiel: (Just ’a’ == Just ’b’) == False
76
Auswertungsreihenfolge
inc :: Int -> Int
inc n = n + 1
2 Möglichkeiten, den Wert von inc(3 * 5) zu berechnen
Es wird bei beiden Möglichkeiten derselbe Wert berechnet.
(Haskell ist nebenwirkungsfrei.)
sq :: Int -> Int
sq x = x * x
sq (3 + 1)
77
Redex
Reduktion Termersetzung durch Funktionsanwendung
Redex reduzierbarer Teilterm
Redexe von mult( 1 + 2, 2 + 3 )
mult :: Int -> Int -> Int
mult = \x \y -> x * y
mult ( 1 + 2 )( 2 + 3 )
fst :: (a, b) -> a
fst ( x, _ ) = x
fst ( 1 + 2, 3 + 5 )
78
Auswertungs-Strategien
innermost Reduktion von Redexen, die keinen Redex
enthalten
(Parameterübergabe by value)
outermost Reduktion von Redexen, die in keinem Redex
enthalten sind
(Parameterübergabe by name)
(jeweils so weit links wie möglich zuerst)
Teilterme in λ-Ausdrücken werden nicht reduziert.
mult :: Int -> Int -> Int
mult x = \y -> x * y
mult ( 1 + 2 )( 2 + 3 )
(\ x -> 1 + 2) 1
79
Termination
inf :: Int
inf = 1 + inf
Auswertung von
fst (0, inf)
terminiert unter outermost-Strategie,
aber nicht unter innermost-Strategie
Fakt
Für jeden Ausdruck, für den die Auswertung unter irgendeiner
Strategie terminiert, terminert auch die Auswertung unter
outermost-Strategie.
80
Sharing von Teilausdrücken
jeder Funktionsaufruf ist lazy:
I
kehrt sofort zurück
I
Resultat ist thunk
I
thunk wird erst bei Bedarf ausgewertet
I
Bedarf entsteht durch Pattern Matching
sq :: Int -> Int
sq x = x * x
sq (3 + 1)
data N = Z | S N
nichtnull :: N -> Bool
nichtnull n = case n of
Z -> False ; S _ -> True
x = S (error "42")
nichtnull x
Lazy Evaluation (Bedarfsauswertung) =
Outermost-Reduktionsstrategie mit Sharing
81
Lazyness
jeder Wert wird erst bei Bedarf ausgewertet.
Listen sind Streams, der Tail wird erst bei Bedarf ausgewertet.
Wann die Auswertung stattfindet, lässt nicht beobachten.
Die Auswertung hat keine Nebenwirkungen.
unendliche Datenstruktur:
naturals :: [ Integer ]
naturals = from 0 where
from x = x : from (x+1)
Liste aller Quadratzahlen? Primzahlen?
82
Unendliche Datenstrukturen
inf :: Int
inf = 1 + inf
fst(0, inf)
einsen :: [Int]
einsen = 1 : einsen
head einsen
take 3 einsen
nats :: [Int]
nats = 0 : map (+1) nats
takeWhile (<= 5) nats
83
Motivation: Datenströme
Folge von Daten:
I
I
I
erzeugen (producer)
transformieren
verarbeiten (consumer)
aus softwaretechnischen Gründen diese drei Aspekte im
Programmtext trennen,
aus Effizienzgründen in der Ausführung verschränken
(bedarfsgesteuerter Transformation/Erzeugung)
84
Rekursive Stream-Definitionen
nats = 0 : map (+1) nats
fibonacci = 0
: 1
: zipWith (+) fibonacci ( tail fibonacci )
take 10 fibonacci
take 1 $ dropWhile (< 200) fibonacci
Welchen Wert hat bin ?
bin = False
: True
: concat ( map ( \ x -> [ x, not x ] )
( tail bin ) )
85
Primzahlen
Sieb des Eratosthenes
alle_ab :: Int -> [ Int ]
alle_ab n = n : alle_ab ( n+1 )
primzahlen :: [ Int ]
primzahlen = sieb $ alle_ab 2
sieb :: [ Int ] -> [ Int ]
sieb (x : xs) = x : ...
take 100 primzahlen
takeWhile (< 100) primzahlen
86
Strictness
Berechnung eines Funktions-Argumentes vor Anwendung der
Funktion
mitunter auch in Haskell sinnvoll
(z.B. Zeit- und Platzbeschränkung der Berechnung)
Syntax: f $! x berechnet denselben Wert wie fx, abe in
anderer Auswertungsreihenfolge:
f $! x wird erst dann ein Redex (und reduziert zu fx), sobald
nach (lazy) Auswertung x kein undefinierter Wert mehr ist.
in Haskell:
I
Konstruktoren (Cons, . . . ) sind nicht strikt,
I
Destruktoren (head, tail, . . . ) sind strikt.
für Funktionen mit mehreren Argumenten:
Striktheit in jedem Argument einzeln.
87
Strikte Auswertung
Bei bekannter Striktheit kann der Compiler effizienteren Code
erzeugen (frühe Argumentauswertung)
z.B. Listen-Summe mit Akkumulator:
sum_n :: Int -> [Int] -> Int
sum_n n [] = n
sum_n n ( x : xs ) = sum_n (n + x) xs
sum_n 0 [ 1,2,3]
sum_n 0 [ 1 .. 100000]
strikte Auswertung eines Teiltermes erzwingen:
sum_n n ( x : xs ) = (sum_n $!(n + x)) xs
strikte Version von foldl
sfoldl :: (a -> b -> a) -> a -> [b] -> a
sfoldl f x [] = x
sfoldl f x (y : ys) = ((sfoldl f) $! (f x y)) ys
88
Herunterladen