Programmieren in Haskell Programmiermethodik

Werbung
Programmieren in Haskell
Programmiermethodik
Peter Steffen
Universität Bielefeld
Technische Fakultät
16.01.2009
1
Programmieren in Haskell
Bisherige Themen
Was soll wiederholt werden?
Bedienung von hugs oder ghc
Grundlegende Haskell-Funktionen
Syntax und Semantik von Haskell
Datentypen
Typklassen
Musik
Lokale Definitionen mit where und let
Programmieren mit Listen
foldr und Kolleginnen
Arrays
Hash-Tabellen
Ein- und Ausgabe
Abstrakte Datentypen
2
Programmieren in Haskell
Spezifikation
Aus dem Schülerduden Informatik:
Präzise Darstellung und Beschreibung der Eigenschaften, der
Verhaltensweisen, des Zusammenwirkens und des Aufbaus von
Modellen, Programmen oder Systemen, wobei meist von Details
abstrahiert wird und konkrete Implementierungen keine Rolle spielen.
Die Spezifikation stellt im Idealfall eine vollständig formale, von
Implementierungen unabhängige Beschreibung des Verhaltens eines zu
erstellenden Systems dar.
Das Ziel einer Spezifikation liegt erstens in einem klaren Verständnis
des Problems, seiner besonderen Eigenschaften und seiner Lösungen.
Zweitens dient sie als Vergleichsmaß dafür, ob ein System oder
Programm den gewünschten Anforderungen genügt.
3
Programmieren in Haskell
Spezifikation des Sortierproblems
Informell: Gesucht ist eine Funktion sort, die eine gegebene Liste von
Elementen aufsteigend anordnet.
Beispiele:
sort
sort
sort
sort
4
[8, 3, 5, 3, 6, 1]
"hello world"
["Bein", "Anfall", "Anna"]
[(1, 7), (1, 3), (2, 2)]
⇒
⇒
⇒
⇒
[1, 3, 3, 5, 6, 8]
" dehllloorw"
["Anfall", "Anna", "Bein"]
[(1, 3), (1, 7), (2, 2)]
Programmieren in Haskell
Spezifikation der Sortierfunktion
sort :: (Ord a) => [a] -> OrdList a
ordered
ordered
ordered
ordered
:: (Ord a)
[]
[a]
(a1:a2:as)
=> [a] -> Bool
= True
= True
= a1 <= a2 && ordered (a2:as)
Spezifikation 1: Für alle Listen x :: [τ ] muß gelten:
ordered (sort x) = True .
Reicht das?
Nein:
sort xs = []
5
erfüllt ebenfalls die Spezifikation
Programmieren in Haskell
(1)
Spezifikation der Sortierfunktion
sort :: (Ord a) => [a] -> OrdList a
ordered
ordered
ordered
ordered
:: (Ord a)
[]
[a]
(a1:a2:as)
=> [a] -> Bool
= True
= True
= a1 <= a2 && ordered (a2:as)
Spezifikation 1: Für alle Listen x :: [τ ] muß gelten:
ordered (sort x) = True .
Reicht das?
Nein:
sort xs = []
5
erfüllt ebenfalls die Spezifikation
Programmieren in Haskell
(1)
Spezifikation der Sortierfunktion
sort :: (Ord a) => [a] -> OrdList a
ordered
ordered
ordered
ordered
:: (Ord a)
[]
[a]
(a1:a2:as)
=> [a] -> Bool
= True
= True
= a1 <= a2 && ordered (a2:as)
Spezifikation 1: Für alle Listen x :: [τ ] muß gelten:
ordered (sort x) = True .
Reicht das?
Nein:
sort xs = []
5
erfüllt ebenfalls die Spezifikation
Programmieren in Haskell
(1)
Spezifikation der Sortierfunktion
sort :: (Ord a) => [a] -> OrdList a
ordered
ordered
ordered
ordered
:: (Ord a)
[]
[a]
(a1:a2:as)
=> [a] -> Bool
= True
= True
= a1 <= a2 && ordered (a2:as)
Spezifikation 1: Für alle Listen x :: [τ ] muß gelten:
ordered (sort x) = True .
Reicht das?
Nein:
sort xs = []
5
erfüllt ebenfalls die Spezifikation
Programmieren in Haskell
(1)
Multimengen
∅ die leere Multimenge,
*a+ die einelementige Multimenge, die genau ein Vorkommen von
a enthält,
x ] y die Vereinigung der Elemente von x und y ; das +“ im
”
Vereinigungszeichen deutet an, dass sich die Vorkommen in x
und y akkumulieren.
∅]x
x ]∅
x ]y
(x ] y ) ] z
6
=
=
=
=
x
x
y ]x
x ] (y ] z)
Programmieren in Haskell
(2)
(3)
(4)
(5)
Multimengen
bag :: [a] -> Bag a
bag [] = ∅
bag (a:as) = *a+ ] bag as
Eine Liste x enthält alle Elemente von y , falls bag x = bag y . In diesem
Fall heißt x Permutation von y .
Spezifikation 2: Für alle Listen x :: [τ ] muß gelten:
ordered (sort x) = True ∧ bag (sort x) = bag x .
(6)
Dadurch ist sort als mathematische Funktion, nicht aber als Programm,
eindeutig bestimmt.
7
Programmieren in Haskell
Abstrakter Datentyp Multimenge
module Bag (Bag, emptyBag, bagEmpty, inBag, addBag, delBag,
appendBag, headBag, tailBag) where
import List
emptyBag
bagEmpty
inBag
addBag
delBag
appendBag
headBag
tailBag
8
::
::
::
::
::
::
::
::
Bag
Bag
(Eq
(Eq
(Eq
Bag
Bag
Bag
a
a -> Bool
a) => a ->
a) => a ->
a) => a ->
a -> Bag a
a -> a
a -> Bag a
Bag a -> Bool
Bag a -> Bag a
Bag a -> Bag a
-> Bag a
Programmieren in Haskell
Abstrakter Datentyp Multimenge
emptyBag erzeugt eine neue Multimenge
bagEmpty überprüft, ob eine Multimenge leer ist
inBag überprüft, ob ein Element in der Multimenge enthalten ist
addBag fügt ein Element einer Multimenge hinzu
delBag löscht ein Element aus einer Multimenge
appendBag vereinigt zwei Multimengen
headBag gibt das erste Element aus der Multimenge aus
tailBag entfernt das erste Element aus der Multimenge
9
Programmieren in Haskell
Implementierung
emptyBag = Bag []
bagEmpty (Bag []) = True
bagEmpty _
= False
inBag x (Bag xs) = elem x xs
addBag x (Bag xs) = Bag (x:xs)
delBag x (Bag xs) = Bag (filter (/= x) xs)
appendBag (Bag xs) (Bag ys) = Bag (xs ++ ys)
headBag (Bag [])
= error "headBag on empty bag"
headBag (Bag (x:xs)) = x
tailBag (Bag [])
= error "tailBag on empty bag"
tailBag (Bag (x:xs)) = Bag xs
instance (Eq a, Ord a) => Eq (Bag a) where
(Bag xs) == (Bag ys) = sort xs == sort ys
10
Programmieren in Haskell
Strukturelle Rekursion auf Listen
length :: [a] -> Int
length []
= 0
length (a:as) = 1 + length as
-- vordefiniert
Die Funktion length folgt dem Schema der strukurellen Rekursion. Für
jeden Konstruktor des Datentyps, [] und (:), gibt es eine Gleichung. Der
Konstruktor (:) ist rekursiv im zweiten Argument, über dieses Argument
erfolgt der rekursive Aufruf. Man sagt, die Funktion ist strukturell rekursiv
definiert.
11
Programmieren in Haskell
Strukturelle Rekursion auf Listen
Rekursionsbasis: []
Rekursionsschritt: (a:as)
Schema der strukturellen Rekursion auf Listen:
f
:: [σ] -> τ
f []
= e1
f (a : as)
= e2
where s = f as
Dabei sind e1 und e2 Ausdrücke vom Typ τ und e2 darf die Variablen a, as
und s (nicht aber f ) enthalten. Mit s wird gerade die Lösung für as
bezeichnet. Tritt s nur einmal in e2 auf, kann man natürlich für s auch
direkt f s einsetzen.
12
Programmieren in Haskell
Sortieren durch Einfügen
insertionSort
:: (Ord a) => [a] -> OrdList a
insertionSort []
= e1
insertionSort (a : as) = e2
where s
= insertionSort as
insertionSort :: (Ord a) => [a] -> OrdList a
insertionSort []
= []
insertionSort (a:as) = insert a s
where s
= insertionSort as
13
Programmieren in Haskell
Sortieren durch Einfügen
insertionSort
:: (Ord a) => [a] -> OrdList a
insertionSort []
= e1
insertionSort (a : as) = e2
where s
= insertionSort as
insertionSort :: (Ord a) => [a] -> OrdList a
insertionSort []
= []
insertionSort (a:as) = insert a s
where s
= insertionSort as
13
Programmieren in Haskell
Erweitertes Rekursionsschema
Erweitertes Rekursionsschema:
g
:: σ1 -> [σ2 ] -> τ
g i []
= e1
g i (a : as) = e2
where s
= g e3 as
wobei e1 die Variable i, e2 die Variablen i, a, as und s und e3 die
Variablen i, a und as enthalten darf.
insert
:: (Ord a) => a -> OrdList a -> OrdList a
insert a []
= e1
insert a (a’ : as) = e2
where s
= insert e3 as
14
Programmieren in Haskell
Insert
insert a []
insert a (a’ : as)
| a <= a’
| otherwise
where s
= [a]
= e21
= e22
= insert e3 as
insert :: (Ord a) => a -> [a] -> [a]
insert a []
= [a]
insert a (a’:as)
| a <= a’
= a:a’:as
| otherwise = a’:insert a as
15
Programmieren in Haskell
Strukturelle Rekursion auf Bäumen
data Tree a = Nil
| Leaf a
| Br (Tree a) (Tree a)
deriving Show
Rekursionsbasis (Nil) Das Problem wird für den leeren Baum gelöst.
Rekursionsbasis (Leaf a) Das Problem wird für das Blatt Leaf a
gelöst.
Rekursionsschritt (Br l r) Um das Problem für den Baum Br l r zu
lösen, werden rekursiv Lösungen für l und r bestimmt, die
zu einer Lösung für Br l r erweitert werden.
16
Programmieren in Haskell
Strukturelle Rekursion auf Bäumen
Schema der strukturellen Rekursion auf Bäumen:
f
:: Tree σ -> τ
f Nil
= e1
f (Leaf a)
= e2
f (Br l r)
= e3
where sl = f l
sr = f r
e3 darf dabei l, r, sl und sr enthalten, nicht aber f .
17
Programmieren in Haskell
Funktionen auf Bäumen
size berechnet die Anzahl der Blätter eines Baumes:
size
size
size
size
:: Tree a -> Integer
Nil
= 1
(Leaf _) = 1
(Br l r) = size l + size r
depth berechnet die Tiefe des Baumes, d.h. die Länge des längsten Pfades
von der Wurzel bis zu einem Blatt:
depth
depth
depth
depth
18
:: Tree a -> Integer
Nil
= 0
(Leaf _) = 0
(Br l r) = max (depth l) (depth r) + 1
Programmieren in Haskell
Das allgemeine Rekursionsschema
data T a1 . . . am
= C1 t11 . . . t1n1
| ...
| Cr tr 1 . . . trnr
Wir unterscheiden zwei Arten von Argumenten: rekursive (d.h. tij ist gleich
T a1 . . . am ) und nicht-rekursive.
Seien li1 ,. . . , lipi mit 1 6 li1 < li2 < · · · < lipi 6 ni die Positionen, an
denen der Konstruktor Ci rekursiv ist
19
Programmieren in Haskell
Strukturelle Rekursion
Allgemeines Schema der strukturellen Rekursion:
f
:: T σ1 . . . σm -> τ
f (C1 x11 . . . x1n1 ) = e1
where s11
= f x1l11
...
s1p1
= f x1l1p1
...
f (Cr xr 1 . . . xrnr ) = er
where sr 1
= f xrlr 1
...
srpr
= f xrlrpr
Der Ausdruck ei darf die Variablen xi1 , . . . , xini und die Variablen
si1 , . . . , sipi enthalten. Ist pi = 0, so spricht man von einer Rekursionsbasis,
sonst von einem Rekursionsschritt.
20
Programmieren in Haskell
Verstärkung der Rekursion
Wir wollen eine Funktion entwickeln, die eine Liste von Elementen
umkehrt. Wenn wir das Schema der strukturellen Rekursion anwenden,
ergibt sich folgende Implementierung:
reverse’’ :: [a] -> [a]
-- vordefiniert
reverse’’ []
= []
reverse’’ (a:as) = reverse’’ as ++ [a]
Problem: In jedem Rekursionsschritt wird von der Funktion ++ die
komplette bereits umgedrehte Liste durchlaufen. Die Laufzeit von
reverse’’ ist somit in O(n2 ).
21
Programmieren in Haskell
Verstärkung der Rekursion
Wir wollen eine bessere Implementierung von reverse systematisch
herleiten. Idee: Wir programmieren eine Funktion, die ein schwierigeres
Problem löst als verlangt, aber die es uns erlaubt, den Rekursionsschritt
besser zu bewältigen. Diese Technik nennt man Verstärkung der
”
Rekursion” oder Programmieren durch Einbettung”.
”
Für reverse bedeutet dies: Im Rekursionsschritt müssen wir die Restliste
umdrehen und an das Ergebnis eine Liste anhängen. Idee: Wir entwickeln
eine Funktion, die beide Aufgaben gleichzeitig löst:
Spezifikation:
reel
:: [a] -> [a] -> [a]
reel x y = reverse x ++ y
Aus dieser Spezifikation können wir die Definition von reel systematisch
ableiten.
22
Programmieren in Haskell
Verstärkung der Rekursion
Rekursionsbasis (x = []):
reel [] y
= reverse [] ++ y (Spezifikation)
=
[] ++ y
(Def. reverse)
=
y
(Def. (++))
23
Programmieren in Haskell
Verstärkung der Rekursion
Rekursionsschritt (x = a:as):
=
=
=
=
=
24
reel (a:as) y
reverse (a:as) ++ y
(reverse as ++ [a]) ++ y
reverse as ++ ([a] ++ y)
reverse as ++ (a:y)
reel as (a:y)
(Spezifikation)
(Def. reverse)
(Ass. (++))
(Def. (++))
(Spezifikation)
Programmieren in Haskell
Verstärkung der Rekursion
reel :: [a] -> [a] -> [a]
reel []
y = y
reel (a:as) y = reel as (a:y)
reverse xs
= reverse xs ++ [] (Def. (++))
=
(reel xs [])
(Spezifikation)
reverse’’’ :: [a] -> [a]
reverse’’’ as = reel as []
25
Programmieren in Haskell
Verstärkung der Rekursion
reel :: [a] -> [a] -> [a]
reel []
y = y
reel (a:as) y = reel as (a:y)
reverse xs
= reverse xs ++ [] (Def. (++))
=
(reel xs [])
(Spezifikation)
reverse’’’ :: [a] -> [a]
reverse’’’ as = reel as []
25
Programmieren in Haskell
Verstärkung der Rekursion
reel :: [a] -> [a] -> [a]
reel []
y = y
reel (a:as) y = reel as (a:y)
reverse xs
= reverse xs ++ [] (Def. (++))
=
(reel xs [])
(Spezifikation)
reverse’’’ :: [a] -> [a]
reverse’’’ as = reel as []
25
Programmieren in Haskell
Herunterladen