Foliensatz 6 (4 auf 1)

Werbung
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
3. Funktionales Programmieren
Begriffsklärung:
3.1 Grundkonzepte funktionaler Programmierung
Weitere Operationen auf neu deklarierten Typen:
Konstruktorfunktionen oder Konstruktoren liefern Werte des neu
definierten Datentyps. Sie können in Mustern verwendet werden (z.B.:
Student, Intc).
Konstruktoren und Selektoren erlauben das Aufbauen und Zerlegen
der Werte neu deklarierter Typen. Durch den Zusatz:
deriving (Eq ,Show)
Diskriminatorfunktionen oder Diskriminatoren prüfen, ob der Wert
eines benutzerdefinierten Datentyps zu einer bestimmten Alternative
gehört (Beispiel: isInt).
liefert Haskell auch eine standardmäßige Gleichheit und die
Möglichkeit, Werte des neuen Typs mittels print auszugeben. Zum
Beipiel:
Selektorfunktionen oder Selektoren liefern Komponenten von
Werten des definierten Datentyps (z.B.: vorname, name, . . . ).
data MyNumber = Intc
Int
| Floatc Float
deriving (Eq ,Show)
Bemerkung:
In funktionalen Sprachen kann man meist auf Selektorfunktionen
verzichten. Man verwendet stattdessen Muster/Pattern.
©Arnd Poetzsch-Heffter
TU Kaiserslautern
3. Funktionales Programmieren
Bemerkung:
Haskell ermöglich es dem Benutzer auch, die Gleichheit oder andere
Operationen auf neu definierten Typen selbst zu definieren.
281
©Arnd Poetzsch-Heffter
3.1 Grundkonzepte funktionaler Programmierung
3. Funktionales Programmieren
Weitere Anwendungen der datatype-Deklaration:
3.1 Grundkonzepte funktionaler Programmierung
data Wochentag =
Montag | Dienstag | Mittwoch | Donnerstag
| Freitag | Samstag | Sonntag
deriving (Eq ,Show)
istMittwoch :: Wochentag -> Bool
istMittwoch Mittwoch = True
istMittwoch _
= False
Die Wertemenge eines Aufzählungstyps ist eine endliche Menge (von
Namen).
Oder knapper:
istMittwoch w
TU Kaiserslautern
282
Beispiel: (Aufzählungstypen)
Die datatype-Deklaration kann auch verwendet werden, um
Aufzählungstypen zu definieren, indem nur null-stellige
Konstruktoren benutzt werden.
©Arnd Poetzsch-Heffter
TU Kaiserslautern
283
©Arnd Poetzsch-Heffter
=
(w== Mittwoch )
TU Kaiserslautern
284
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
3. Funktionales Programmieren
Konstruktoren mit beliebiger Stelligkeit
Rekursive Datentypen
In einer Datentypdeklaration können Konstruktoren mit beliebiger
Stelligkeit kombiniert werden; z.B.:
data
MaybeInt =
|
Von großer Bedeutung in allen Paradigmen der Programmierung sind
rekursive Datentypen. Sie erlauben es insbesondere:
Nothing
Some Int
• Listen beliebiger Länge
• Bäume beliebiger Höhe
Haskell sieht dafür im Prelude den folgenden parametrisierten Typ vor
(vgl. 3.3):
data Maybe a
©Arnd Poetzsch-Heffter
=
|
behandeln zu können.
Nothing
Some a
TU Kaiserslautern
3. Funktionales Programmieren
285
©Arnd Poetzsch-Heffter
3.1 Grundkonzepte funktionaler Programmierung
286
3.1 Grundkonzepte funktionaler Programmierung
Beispiele: (Listendatentypen)
Eine Datentypdeklaration heißt direkt rekursiv, wenn der neu
definierte Typ in einer der Alternativen der Datentypdeklaration
vorkommt.
1. Ein Datentyp für Integer-Listen:
data
Wie bei Funktionen gibt es auch verschränkt rekursive
Datentypdeklarationen.
Intlist =
nil
| Cons Int Intlist
2. Ein Datentyp für homogene Listen mit Elementen von beliebigem
Typ:
Eine Datentypdeklaration heißt rekursiv, wenn sie direkt rekursiv ist
oder Element einer Menge verschränkt rekursiver
Datentypdeklarationen ist.
data List a
Ein Datentyp heißt rekursiv, wenn er mit einer rekursiven
Datentypdeklaration definiert wurde.
TU Kaiserslautern
TU Kaiserslautern
3. Funktionales Programmieren
Definition: (rekursive Datentypen)
©Arnd Poetzsch-Heffter
3.1 Grundkonzepte funktionaler Programmierung
|
287
©Arnd Poetzsch-Heffter
=
nil
Cons
a (List a)
TU Kaiserslautern
288
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
3. Funktionales Programmieren
Baumartige Datenstrukturen:
3.1 Grundkonzepte funktionaler Programmierung
Begriffsklärungen: (zu Bäumen)
• In einem endlich verzweigten Baum hat jeder Knoten endlich
Ottmann, Widmayer:
„Bäume gehören zu den wichtigsten in der Informatik auftretenden
Datenstrukturen“.
viele Kinder.
• Üblicherweise sagt man, die Kinder sind von links nach rechts
geordnet.
• Einen Knoten ohne Kinder nennt man ein Blatt, einen Knoten mit
Kindern einen inneren Knoten oder Zweig.
• Den Knoten ohne Elter nennt man Wurzel.
• Ein Baum heißt markiert, wenn jeder Knoten k eine Markierung
m(k ) besitzt.
• In einem Binärbaum hat jeder Knoten maximal zwei Kinder.
• Zu jedem Knoten k gehört ein Unterbaum, nämlich der Baum der
k als Wurzel hat.
©Arnd Poetzsch-Heffter
TU Kaiserslautern
3. Funktionales Programmieren
289
3.1 Grundkonzepte funktionaler Programmierung
290
3.1 Grundkonzepte funktionaler Programmierung
Definition: (Sortiertheit markierter Binärbäume)
data
IntBBaum =
Blatt Int
| Zweig Int IntBBaum IntBBaum
deriving (Eq ,Show)
Ein mit ganzen Zahlen markierter Binärbaum heißt sortiert, wenn für
alle Knoten k gilt:
• Alle Markierungen der linken Nachkommen von k sind kleiner als
einbaum = Zweig 7 ( Zweig 3 ( Blatt 2) (Blatt 4)) (Blatt 5)
m(k ).
• Alle Markierungen der rechten Nachkommen von k sind größer
mark :: IntBBaum -> Int
mark ( Blatt n)
= n
mark ( Zweig n lk rk) = n
TU Kaiserslautern
TU Kaiserslautern
3. Funktionales Programmieren
Datentyp für markierte Binärbäume:
©Arnd Poetzsch-Heffter
©Arnd Poetzsch-Heffter
als m(k ).
291
©Arnd Poetzsch-Heffter
TU Kaiserslautern
292
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
3. Funktionales Programmieren
Prüfung von Sortiertheit
Prüfung von Sortiertheit (2)
istsortiert :: IntBBaum
istsortiert (Blatt n)
istsortiert (Zweig n lk
istsortiert lk &&
( maxmark lk)<n &&
Aufgabe:
Prüfe, ob ein Baum vom Typ IntBBaum sortiert ist.
Idee:
Berechne zu jedem Unterbaum die minimale und maximale
Markierung und prüfe rekursiv die Sortiertheitseigenschaft für alle
Knoten/Unterbäume.
Wenig effiziente Lösung! Besser ist es, die Berechnung von Minima
und Maxima mit der Sortiertheitsprüfung zu verschränken.
Idee:
Entwickle eine Funktion istsortiert3 mit drei Ergebniswerten:
• Angabe, ob Baum sortiert
minmark ( Blatt n)
= n
minmark ( Zweig n lk rk) =
n `min` ( minmark lk `min` minmark rk)
TU Kaiserslautern
3. Funktionales Programmieren
-> Bool
= True
rk) =
istsortiert rk &&
n<( minmark rk)
result = istsortiert einbaum
maxmark , minmark :: IntBBaum -> Int
maxmark ( Blatt n)
= n
maxmark ( Zweig n lk rk) =
n `max` ( maxmark lk `max` maxmark rk)
©Arnd Poetzsch-Heffter
3.1 Grundkonzepte funktionaler Programmierung
• minimale Markierung des Baums
• aximale Markierung des Baums
293
©Arnd Poetzsch-Heffter
3.1 Grundkonzepte funktionaler Programmierung
TU Kaiserslautern
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Prüfung von Sortiertheit (3)
Begriffsklärung: (Weitere Begriffe zu Bäumen)
istsortiert3 :: IntBBaum -> (Bool ,Int ,Int)
-- Sei istsortiert3 b == (srt ,mn ,mx) ; dann ist
-srt das Ergebnis der Sortiertheitspruefung von b
-mn die minimale Markierung von b
-mx die maximale Markierung von b
Der leere Baum ist ein Baum ohne Knoten.
Die Tiefe eines Knotens in einem Baum ist sein Abstand zur Wurzel.
Der Wurzelknoten hat die Tiefe 0. Für alle anderen Knoten k gilt:
tiefe(k ) = tiefe(elternknoten(k )) + 1
istsortiert3 (Blatt n)
= (True , n, n)
istsortiert3 ( Zweig n lk rk) =
let (lsrt , lmn , lmx) = istsortiert3 lk
(rsrt , rmn , rmx) = istsortiert3 rk
in (( lsrt && rsrt && lmx <n && n<rmn), lmn , rmx)
Die Knoten gleicher Tiefe t nennt man das Niveau t.
Die Höhe des leeren Baumes ist 0. Die Höhe eines nicht-leeren
Baumes b ist die maximale Knotentiefe plus 1:
höhe( b ) = max { tiefe(k ) | k Knoten von b } + 1.
istsortiert b = let (srtflag ,_ ,_) = ( istsortiert3 b)
in srtflag
©Arnd Poetzsch-Heffter
TU Kaiserslautern
294
Die Größe eines Baums ist die Anzahl seiner Knoten.
295
©Arnd Poetzsch-Heffter
TU Kaiserslautern
296
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
3. Funktionales Programmieren
Datentyp möglicherweise leerer Binärbäume:
3.1 Grundkonzepte funktionaler Programmierung
Bäume mit variabler Kinderzahl:
Bäume mit variabler Kinderzahl lassen sich realisieren:
• durch mehrere Alternativen für Zweige (begrenzte Anzahl von
Kindern)
data IntBBaum2 =
Leer
| Knoten Int IntBBaum2 IntBBaum2
• durch Listen von Unterbäumen:
data VBaum = Kn Int [VBaum]
deriving (Eq ,Show)
Der Rekursionsanfang ergibt sich durch Knoten mit leerer
Unterbaumliste.
©Arnd Poetzsch-Heffter
TU Kaiserslautern
3. Funktionales Programmieren
297
3.1 Grundkonzepte funktionaler Programmierung
TU Kaiserslautern
3. Funktionales Programmieren
Bäume mit variabler Kinderzahl: (2)
298
3.1 Grundkonzepte funktionaler Programmierung
Bemerkungen:
• Bäume mit variabler Kinderzahl lassen sich z.B. zur
zaehleKnVBaum
:: VBaum -> Int
zaehleKnVBaumLst :: [ VBaum ] -> Int
zaehleKnVBaum (Kn _ xs) =
©Arnd Poetzsch-Heffter
Repräsentation von Syntaxbäumen verwenden, indem das
Terminal- bzw. Nichtterminalsymbol als Markierung verwendet
wird.
1 + ( zaehleKnVBaumLst xs)
Besser ist es allerdings, die Information über die Symbole mittels
Konstruktoren auszudrücken (vgl. nächstes Beispiel).
zaehleKnVBaumLst []
= 0
zaehleKnVBaumLst (x:xs) =
( zaehleKnVBaum x) + ( zaehleKnVBaumLst xs)
• Bäume mit variabler Kinderzahl werden auch zur Repräsentation
von strukturierten oder semi-strukturierter Daten verwendet (z.B.
XML, ...)
©Arnd Poetzsch-Heffter
TU Kaiserslautern
299
©Arnd Poetzsch-Heffter
TU Kaiserslautern
300
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
Beispiel: (abstrakte Syntaxbäume)
Beispiel: (abstrakte Syntaxbäume) (2)
Der abstrakte Syntaxbaum eines Programms repräsentiert die
Programmstruktur unter Verzicht auf Schlüsselworte und
Trennzeichen.
Das Femto-Programm
Rekursive Datentypen eignen sich sehr gut zur Beschreibung von
abstrakter Syntax.
a = 73;
main = print ( a + 12 )
Als Beispiel betrachten wir die abstrakte Syntax von Femto:
Wird durch folgenden Baum repräsentiert:
data Programm
deriving
data Wertdekl
deriving
data Ausdruck
Prog
= Prog [ Wertdekl ] Ausdruck
(Eq ,Show)
= Dekl String Ausdruck
(Eq ,Show)
= Bzn String
| Zahl Int
| Add Ausdruck Ausdruck
| Mul Ausdruck Ausdruck
deriving (Eq ,Show)
©Arnd Poetzsch-Heffter
TU Kaiserslautern
3. Funktionales Programmieren
[Dekl "a" (Zahl 73)]
Add (Bzn "a") (Zahl 12)
Die Baumrepräsentation eignet sich besser als die
Zeichenreihenrepräsentation zur weiteren Verarbeitung von
Programmen.
301
©Arnd Poetzsch-Heffter
3.1 Grundkonzepte funktionaler Programmierung
TU Kaiserslautern
3. Funktionales Programmieren
Verschränkte Datentypdeklarationen:
302
3.1 Grundkonzepte funktionaler Programmierung
Beispiel: (verschränkte Datentypen)
Als Beispiel betrachten wir die abstrakte Syntax einer Erweiterung von
Femto um let-Ausdrücke:
data Programm
deriving
data Wertdekl
deriving
data Ausdruck
Haskell unterstützt verschränkt rekursive Datentypdeklarationen.
Die Datentypdeklaration werden einfach hintereinander geschreiben
(wie verschränkt rekursiven Funktionsdeklarationen).
Bei abstrakten Syntaxbäumen wird häufig verschränkte Rekursion der
Datentypen benötigt.
= Prog [ Wertdekl ] Ausdruck
(Eq ,Show)
= Dekl String Ausdruck
(Eq ,Show)
= Bzn String
| Zahl Int
| Add Ausdruck Ausdruck
| Mul Ausdruck Ausdruck
| Let Wertdekl Ausdruck
deriving (Eq ,Show)
Die Deklaration von Wertdekl benutzt Ausdruck; die Deklaration von
Ausdruck benutzt Wertdekl.
©Arnd Poetzsch-Heffter
TU Kaiserslautern
303
©Arnd Poetzsch-Heffter
TU Kaiserslautern
304
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
3. Funktionales Programmieren
Unendliche Datenobjekte:
Begriffsklärung: (Strom)
Zu einer nicht-leeren endlichen Liste kann man sich das erste Element
und eine Liste als Rest geben lassen.
Hat die endliche Liste xl die Länge n > 0, dann hat (tail xl) die
Länge n − 1.
3. Funktionales Programmieren
In Haskell kann man den Listendatentyp zur Realisierung von Strömen
verwenden.
• Lesen des ersten Elements (head)
• Entfernen des ersten Elements (tail)
• Prüfen, ob noch Elemente im Strom vorhanden sind
Haskell unterstützt unendliche Liste und andere unendliche
Datenobjekte.
TU Kaiserslautern
Potenziell unendliche Listen werden meist als Ströme bezeichnet.
Typische Operationen:
Eine unendliche Liste besitzt keine natürlichzahlige Länge und wird
durch Anwendung von tail nicht kürzer.
©Arnd Poetzsch-Heffter
3.1 Grundkonzepte funktionaler Programmierung
Beispiel:
Strom von Eingabedaten
305
©Arnd Poetzsch-Heffter
3.1 Grundkonzepte funktionaler Programmierung
TU Kaiserslautern
3. Funktionales Programmieren
Beispiel: (unendliche Liste)
306
3.1 Grundkonzepte funktionaler Programmierung
Unterabschnitt 3.1.5
Eine vorstellbare unendliche Liste ist die Liste der natürlichen Zahlen
[0,1,2,3,4,5,...]. In Haskell lässt sich diese Liste wie folgt definieren:
incr :: [ Integer ] -> [ Integer ]
incr []
= []
incr (x:xs) = (x+1):(incr xs)
Ein- und Ausgabe
natlist = 0:(incr natlist )
natlist2 = [0 ..]
©Arnd Poetzsch-Heffter
TU Kaiserslautern
307
©Arnd Poetzsch-Heffter
TU Kaiserslautern
308
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
3. Funktionales Programmieren
Ein- und Ausgabe
3.1 Grundkonzepte funktionaler Programmierung
Der Typ IO a
Der Haskell Prelude stellt Funktionen zur Verfügung, um
• Werte auf der Standardausgabe auszugeben und
Um Funktionen mit Ein- und Ausgabe von Funktionen ohne Ein- und
Ausgabe zu trennen und die richtige Reihenfolge zu garantieren,
benutzt Haskell den vordefinierten parametrischen Typ
• aus Dateien zu lesen und in Dateien zu schreiben.
IO a
• Werte von der Standardeingabe zu lesen,
Problem:
In einer rein funktionalen Sprache wie Haskell haben Funktionen keine
Seiteneffekte. Deshalb spielt auch die Reihenfolge des Aufrufs eine
weniger wichtige Rolle.
Zum Beispiel hat die Funktion putStr zur Ausgabe einer Zeichenreihe
die Signatur:
Ein- und Ausgabe erzeugen einen Seiteneffekt auf die
Programmumgebung. Sie müssen in der richtigen Reihenfolge
ausgeführt werden.
Zeichenreihe in die Standardausgabe (erzeugt also einen
IO-Seiteneffekt) und liefert die Einheit als Ergebnis.
©Arnd Poetzsch-Heffter
TU Kaiserslautern
3. Funktionales Programmieren
309
putStr :: String -> IO ()
putStr nimmt eine Zeichenreihe als Argument, schreibt die
©Arnd Poetzsch-Heffter
3.1 Grundkonzepte funktionaler Programmierung
TU Kaiserslautern
3. Funktionales Programmieren
Der Typ IO a (2)
310
3.1 Grundkonzepte funktionaler Programmierung
Sequentielle Ausführung von IO-Aktionen
Für die sequentielle Ausführung von IO-Aktionen stellt Haskell die
do-Notation zur Verfügung:
Wir nennen Funktionen und Konstanten mit Ergebnistyp IO a
IO-Aktionen.
do {
Anw1 ;
...
AnwN ;
IO - Aktion
}
Zum Beispiel ist die Konstante/null-stellige Funktion getLine eine
IO-Aktion:
getLine :: IO String
Ausführung der Aktion getLine liest von der Standardeingabe (erzeugt
also einen IO-Seiteneffekt) und liefert eine Zeichereihe.
do
Anw1
...
AnwN
IO - Aktion
Eine Anweisung Anw hat die Form
Bezeichner
<-
IO - Aktion
wobei der Bezeichner und Zuweisungpfeil “<-” entfallen können, wenn
das Ergebnis der IO-Aktion nicht gebraucht wird.
©Arnd Poetzsch-Heffter
TU Kaiserslautern
311
©Arnd Poetzsch-Heffter
TU Kaiserslautern
312
3. Funktionales Programmieren
3.1 Grundkonzepte funktionaler Programmierung
3. Funktionales Programmieren
Beispiel: (Anweisungssequenz)
3.1 Grundkonzepte funktionaler Programmierung
Beispiel: (Anweisungssequenz) (2)
main = do {
hSetBuffering stdout NoBuffering ; -- import System .IO
putStr "Gib eine Zeichenreihe ein: ";
s <- getLine ;
putStr " Revertierte Zeichereihe : ";
putStrLn ( reverse s)
}
Ausgabe aller Elemente einer String-Liste:
printStringList :: [ String ] -> IO ()
printStringList []
= putStr ""
printStringList (s:xs) =
do
putStr s
printStringList xs
Erläuterung:
• Die Anweisung s <- getLine bindet den gelesenen Wert an s.
• Da die letzte IO-Aktion den Typ IO () hat, hat auch main den Typ
IO ().
©Arnd Poetzsch-Heffter
TU Kaiserslautern
3. Funktionales Programmieren
313
3.1 Grundkonzepte funktionaler Programmierung
314
3.1 Grundkonzepte funktionaler Programmierung
Dateiein- und -ausgabe
Bisher haben wir nur IO-Aktion für Zeichenreihen kennen gelernt.
Zum Lesen aus und Schreiben in Dateien stellt Haskell u. a. die
IO-Aktionen:
Haskell unterstützt im Prelude und Bibliotheken viele weitere
IO-Aktionen. Z. B.:
readFile :: FilePath -> IO String
writeFile :: FilePath -> String -> IO ()
getChar :: IO Char
putChar :: Char -> IO ()
Dabei ist FilePath eine Zeichenreihe, die einen Dateinamen
bezeichnet:
Und für einen lesbaren und anzeigbaren Typ a die parametrischen
IO-Aktionen:
type FilePath = String
readLn :: IO a
print :: a -> IO ()
TU Kaiserslautern
TU Kaiserslautern
3. Funktionales Programmieren
Parametrisierte IO-Aktionen
©Arnd Poetzsch-Heffter
©Arnd Poetzsch-Heffter
315
©Arnd Poetzsch-Heffter
TU Kaiserslautern
316
Herunterladen