Seminar Programmiersprachen

Werbung
1
Seminar Programmiersprachen
Curry
Autor:
Björn Weinbrenner
Betreuer:
Prof. Dr. Herbert Kuchen
2
Inhaltsverzeichnis
1.
Allgemeines
2.
Typsystem
3.
Funktionen
4.
Suche
5.
I/O
6.
Quick Sort
7.
Fazit
3
1. Allgemeines
Einordnung
•
deklarative Programmiersprache
•
funktional-logische Programmiersprache
•
Programm beschreiben das Problem, nicht den Lösungsweg
•
Die Lösungsfindung ist Aufgabe des Computers
4
Funktionale Programmiersprachen
•
basieren auf dem Funktionsbegriff der Mathematik
•
Ein Ausdruck besteht aus verschachtelten Funktionsaufrufen
•
Ausdrücke einer funktionalen Sprache werden deterministisch reduziert
•
Am Ende der Berechnung steht ein Ausdruck, der nur Konstruktoren
enthält
•
keine Schleifen
•
Häufige Verwendung rekursiver Funktionen
•
Funktionen können höherer Ordnung sein
5
Logische Programmierung
•
Ausdrücke sind Suchziele, die Unbekannte enthalten.
•
Das Ergebnis der Berechnung sind Bindungen für diese Unbekannten
•
Die Berechnung selbst ist die Suche nach diesen Bindungen
•
Dabei führen Ausdrücke ggf. zu nichtdetermistischen Berechnungen
6
Funktional-logische Sprachen
•
vereinen die Eigenschaften der beiden Programmierparadigmen
•
vereinendes Sprachkonstrukt sind sog. Constraints (Randbedingungen)
•
Auswertung dieser Constraints durch zwei wichtige operationale Prinzipien:
Residuation und Narrowing
•
Anwendung je nach Art der Funktionen, die innerhalb des Constraints
aufgerufen werden
7
Entstehungsgeschichte der Sprache
•
Benennung nach dem Logiker Haskell B. Curry, nach dem auch die
funktionale Sprache Haskell benannt ist
•
Curry ist praktisch eine Erweiterung der Sprache Haskell
•
Entwicklung durch internationale Initiative von Hochschulen
•
Erfolgreiche Nutzung im Bereich der Hochschullehre, um logische und
funktionale Programmierung mit einer Sprache zu lehren
•
Webseite mit praktisch allen Informationen zum Thema Curry:
http://www.informatik.uni-kiel.de/~curry/
•
Grundlagendokumente für Programmierer und Softwareentwickler
8
Entwicklungsumgebung
•
Siehe "Report on Curry" [Hanus]
•
interaktive Programmumgebung in Form eines
Kommandozeileninterpreters
•
Programmdateien können geladen werden
•
Keine direkte Ausführung, wie in anderen Sprachen üblich
•
Stattdessen Benutzereingabe eines Ausdrucks über die Kommandozeile
•
Auswertung mit Hilfe der Definitionen in den geladenen Dateien
9
Implementierungen
•
Es gibt wenige Implementierungen der Sprache Curry
•
Bekannteste und am weitesten entwickelte Implementierung ist das
PAKCS (Portland Aachen Kiel Curry System)
•
Münster Curry von Wolfgang Lux ist die schnellste Curry-Umgebung
basierend auf C
•
Implementierungen enthalten Cross-Compiler nach PROLOG, C oder Java,
weshalb ist u.U. ein weiterer Compiler benötigt wird
•
Es gibt keine Implementierung für Windows
10
Syntax
•
Die Syntax der Sprache entspricht weitesgehend der Syntax von Haskell
•
Layout-Regel: Relevanz, wie weit eine Zeile eingerückt ist
•
Literative Programmierung möglich
11
Literative Programmierung
•
"Normale" Schreibweise *.curry-Datei
-- Jetzt kommt eine Funktion
f x = x * x
•
literative Schreibweise in *.lcurry-Datei
<i>Jetzt kommt eine Funktion</i>
<pre>
> f x = x * x
</pre>
12
Curry-Programme
•
Menge von Typdeklarationen und Funktionsdefinitionen
13
Beispiel:
data Bool = True | False
and :: Bool -> Bool -> Bool
and True y = y
and False _ = False
or :: Bool -> Bool -> Bool
or True _ = True
or False y = y
not :: Bool -> Bool
not True = False
not False = True
14
Benutzeranfragen
•
Der Benutzer stellt nun Fragen in Form von Ausdrücken an das System:
and (or True False) (and False True)
•
Auswertung zu false
•
Nur Nutzung des funktionalen Teils der Sprache
15
Benutzeranfragen
and (or True False) (and X True) =:= True
•
Die Ausgabe im PAKCS lautet darauf:
Free variables in goal: X
Result: success
Bindings:
X=True ?
•
Eingegebener Ausdruck ist ein Contraint
•
Nutzung des logischen Teils der Sprache
•
Auswertung zu success, wenn Bindungen für freien Variable gefunden,
werden, so dass die Constraint-Gleichung erfüllt ist.
16
2. Typsystem
•
Verwendung eines Hindley-Milner-ähnliches polymorphes Typsystem.
•
Typnotationen sind nicht erforderlich, solange der Compiler alle fehlenden
Typen aus dem Kontext des Programms rekonstruieren kann
not :: Bool -> Bool (nicht erforderlich)
not True = False
not False = True
•
Viele Fehler können daher beim Kompilieren festgestellt werden.
17
Typsystem
•
Jedes Objekt hat einen eindeutigen Typ
•
Existenz eines allgemeinen Typs, der durch eine Typvariable ausgedrückt
wird
•
Beispiel Identitätsfunktion:
id :: a -> a
id x = x
•
Anwendung auf alle Typen möglich
•
Der Rückgabewert entspricht dem dem Typ des übergebenen Parameters
18
Polymorphismus
•
Das beschriebene Konstrukt wird parametrischer Polymorphismus
genannt.
•
Keine Unterstützung von Ad-hoc-Polymorphismus (Überladen von
Funktionen)
•
Die Funktion (+) kann z.B. nur auf Integerwerte angewandt werden
•
Für Float-Werte muss eine andere Funktion definiert sein.
19
Typdeklaration
•
Es gibt viele Möglichkeiten, eigene Typen zu deklarieren
•
Beispiel:
data Bool = True | False
•
Bool ist dabei der Typkonstruktor, True und False sind
Datenkonstruktoren
20
Rekursive Typdeklaration
•
Es ist erlaubt Typen rekursiv zu definieren
•
Eine Liste vom Typ a ist dann folgendermaßen definiert:
data list a = nil | cons a (list a)
•
Eine Liste ist eine leere Liste oder ein Kopfelement gefolgt von einer Liste,
cons fügt ein einzelnes Element und eine Liste zusammen.
•
Es lassen sich in Curry mit wenig Code komplexe Datenstrukturen (z.B.
Bäume) definieren.
21
Standardtypen / Listen
•
Standardtypen: Bool, Int, Float, Char, String.
•
Listen sind geordnete Zusammenstellungen von Daten ähnlich zu Arrays
•
Die Elemente einer Liste haben alle den selben Typ
•
Listen sind rekursiv definiert, ähnlich wie im obigen Beispiel
•
Statt list a einfachere Schreibweise [a] für Liste vom Typ a
•
type String = [Char]
(Synonymdefinition)
22
Listen
[]
leere Liste
x:xs
Liste mit Head x und Tail xs
[1,2,3,4,5]
Aufzählung der Glieder
[1..15]
arithmetische Sequenz
[1..]
unendliche Liste
[1,3..]
Liste ungerader Zahlen
[x * x | x <- [0..]]
Liste von Quadratzahlen
[x | x <- [0..], isPrim x]
Liste von Primzahlen
23
Funktionen
•
Typ einer Funktion wird aus den Typen der Parameter und dem Typ des
Funktionswertes komponiert
•
Beispiel:
add :: Int -> Int -> Int
•
Diese Notation ist eine andere Abkürzung für
add :: Int -> (Int -> Int)
24
Partielle Anwendung
•
Wenn eine Funktion mit weniger Parametern aufgerufen wird als Ihr Typ
erwartet, wird sie wiederum zu einer Funktion ausgewertet
•
Beispiel:
succ :: Int -> Int
succ = add 1
•
add wird nur mit einem statt mit zwei Parametern aufgerufen
•
Der Ausdruck add 1 ist daher eine Funktion vom Typ Int -> Int
25
Constraints (Randbedingungen)
•
Cointraint sind vom einelementigen Typ Success
•
Den einzigen Wert, den dieser Typ bereitstellt, ist der Konstruktor success
•
Dieser wird zurückgegeben, wenn eine Constraint-Gleichung durch
Bindung der auftretenden freien Variablen erfüllt ist.
26
3. Funktionen
•
Ausdrücke mit einem Namen
•
können Parameter haben
•
Wiederverwendbarkeit
•
Möglichkeit, ein Problem in kleinere Probleme zu gliedern
•
Definition durch eine oder mehrere Funktionsgleichungen
27
Beispiel:
quadrat :: Int -> Int
quadrat x = x * x
•
Funktionsaufruf durch Aneinanderreihung des Funktionsnamens und seiner
Parameter
quadrat 2
28
Pattern-Matching
•
Eine Funktionsgleichung kann auf der linken Seite statt Variablen auch
Konstruktoren haben
•
Beispiel der Funktion not:
not True = False
not False = True
•
Beim Aufruf der Funktion wird für jede Funktionsgleichung geprüft, ob das
"Parameter-Muster" auf den Funktionsaufruf zutrifft
•
Jede passende Gleichung wird dann angewandt
29
Pattern-Matching
•
Sind die Parameter Listen, können sie auch in Doppelpunktnotation
geschrieben werden, so dass direkt auf head und tail zugegriffen werden
kann
head :: [a] -> a
head (x:xs) = x
30
Nicht-Determinismus
•
Werden mehr als eine Funktionsgleichung angewandt, ist die Funktion
nicht deterministisch
•
Sie folgt dann nicht mehr dem Funktionsbegriff der Mathematik
•
Wird eine nichtdetermistische Funktion aufgerufen, die mehr als einen
Rückgabewert hat, wird nacheinander jede Lösung ausgegeben.
31
Beispiel
coin = 0
coin = 1
•
Wird coin aufgerufen, werden nacheinander die Lösungen 0 und 1
ausgegeben
•
2 * coin führt zu den Lösungen 0, 2
•
coin + coin führt zu 0, 1, 1, 2
32
Vordefinierte Operatoren und Funktionen
•
Zuweisungsoperator, arithmetische Operatoren, Vergleichsoperatoren,
Boolsche Operatoren
•
Constraint-Gleichheitsoperator (=:=)
•
Alle Operatoren sind auch Funktionen, d.h. alle erwähnten Eigenschaften
von Funktionen treffen auch auf die Operatoren zu
•
Sind die Operatoren geklammert, gilt die gewohnte Präfixschreibweise von
Funktionen
•
Beispiele:
(+) 2 3
oder (==) x True
33
Einige praktische Operationen auf Listen
•
Zugriff auf das Kopfelement
•
Länge der Liste
•
die Umkehrung der Liste
•
das Zusammenfügen zweier Listen.
34
Konditionale Ausdrücke
•
if_then_else bedingung ausdruck1 ausdruck2
•
if bedingung then ausdruck1 else ausdruck2
•
Guards:
max x y | x > y
= x
|otherwise = y
•
Sukzessive Überprüfung aller Bedingungen bis die erste Bedingung zutrifft
•
otherwise (True) leitet "default case" ein
35
Rekursive Beispiel
•
Fakultätsfunktion
fak n | n == 0 = 1
fak n | n >= 1 = n * (fak (n-1))
•
Fibonacci-Funktion
fib n | n == 0 = 0
fib n | n == 1 = 1
fib n | n >= 2 = (fib (n-2)) + (fib (n-1))
36
Funktionen höherer Ordnung
•
Funktionen die als Parameter oder Funktionswert wiederum Funktionen
haben
•
Beispiel: map wendet eine Funktion auf jedes Glied einer Liste an
map :: (a -> b) -> [a] -> [b]
map f (x:xs) = (f x) : (map f xs)
•
Die Funktion f wird als Parameter übergeben und kann innerhalb von map
verwendet werden
•
map succ [0,1,2] wird z.B. zu [1,2,3] ausgewertet.
37
Lokale Variable und lokale Funktionen
•
Jede Funktion oder Variable hat implizit einen Sichtbarkeitsbereich, in dem
über ihren Bezeichner auf sie zugegriffen werden kann.
•
Funktionsdefinitionen auf oberster Ebene für alle anderen Funktionen
sichtbar
•
Eingrenzung der Sichtbarkeit durch Definition lokaler Variable oder
Funktionen
•
Lokale Variable ist Funktion ohne Parameter und wird einmalig berechnet
•
Zwei Konstrukte: let ... in ... und where
38
let ... in ...
•
Deklaration lokaler Variable und Funktionen im let-Teil
•
Verwendung im in-Teil
•
Beispiel:
let x = 3 in x * x
•
Verwendung in allen Ausdrücken
39
where
•
Verwendung in Funktionsdefinitionen
•
Dem Schlüsselwort where folgen die lokalen Definitionen
•
Beispiel:
f = x * x where x = 3
40
Constraints
•
Ähnliche Notation wie bei Guards möglich
•
Beispiel:
last list | l ++ [x] =:= list = x where x, l free
•
Suche nach Werten für freie Variable x und l, so dass ConstraintGleichung erfüllen ist
•
x und l werden an Werte gebunden und können auf der rechten Seite der
Funktionsgleichung verwendet werden.
41
Verzögerte Auswertung (lazy evaluation)
•
Ausdrücke werden in Curry erst dann berechnet, wenn ihr Wert benötigt
wird
•
Das Gegenteil ist in vielen Sprachen der Fall, wenn Argumente einer
Funktion berechnet werden, bevor die Funktion ausgeführt wird
•
In Curry kann eine unendlich lange Liste an eine Funktion übergeben
werden, ohne dass versucht wird, diese Liste vollständig zu berechnen
•
Es werden nur die Elemente der Liste extrahiert, die benötigt werden.
42
Beispiel
liste = [1..]
head(x:xs) = x
•
Der Aufruf head liste kann in Curry berechnet werden.
43
4. Suche
44
Logische Variable
•
Funktionen können unbekannte Variable beinhalten, wenn diese als free
deklariert sind und in einem Constraint enthalten sind
•
Beispiel:
last list | l ++ [x] =:= list = x
where x, l free
•
Es wird versucht die Unbekannten an Werte zu binden, die die ConstraintGleichheit (=:=) erfüllen
•
Gelingt dies, kann der Aufruf weiter berechnet werden
•
Andernfalls wird die Berechnung abgebrochen mit der Meldung, dass das
Suchziel suspendiert wurde
45
Gekapselte Suche
•
Weitere Möglichkeit, nach Unbekannten zu suchen
•
Sie wird durch einige im Standard enthaltene Funktionen ermöglicht
•
Nicht alle diese Funktionen sind in PAKCS implementiert sind
•
Vorstellung einer dieser Funktionen:
findall :: (a -> Success) -> [a]
46
findall
•
Erwarteter Parameter ist vom Typ (a -> Success), also eine Funktion
von einem Typ a zum Typ Success
•
Eine solche Funktion definiert das Suchziel
•
findall gibt eine Liste zurück, die alle Werte enthält, die an a gebunden
den Constraint erfüllen
•
Beispiel:
goal :: (Bool -> Success)
goal x = (x || True) =:= True
•
Der Aufruf findall goal wird zu [True,False] ausgewertet.
47
Gekapselte Suche
•
Suchvorgang findet in einem gekapselten Ausdruck und nicht global statt
•
Globale Suche ist in manchen Fällen nicht effizient, da sie in der Regel
nach dem Backtracking-Prinzip funktioniert
•
Mit der gekapselten Suche kann mehr Einfluss auf die Art der Suche
genommen werden
•
Diese Möglichkeiten werden allerdings in den aktuellen Implementierungen
noch unzureichend angeboten
48
Suchprinzipien
•
Curry unterstützt die zwei wichtigsten Prinzipien, um Lösungen für logische
Variable zu finden: Residuation und Narrowing
49
Residuation
•
Können freie Variable nicht direkt gebunden werden, werden erst alle
anderen Ausdrücke, aus denen eine Bindung entstehen könnte,
ausgewertet
•
Erst wenn die Variable gebunden ist, wird mit der Auswertung fortgefahren.
•
Ist die freie Variable nicht gebunden und alle anderen Ausdrücke sind
berechnet oder warten auch auf eine Bindung, wird die Berechnung
abgebrochen, das Suchziel wird "suspendiert".
50
Narrowing
•
Unter Verwendung des Narrowing-Prinzips wird versucht, die Bindung der
freien Variablen aus dem Ausdruck herzuleiten
•
Diese Vorgehensweise entspricht dem Auflösen einer mathematischen
Gleichung, auch wenn das in Curry nicht ohne weiteres möglich ist
51
Residuation oder Narrowing
•
Welche Strategie angewendet wird, hängt von der Art der Funktionen ab, in
denen die freien Variablen auftreten
•
Arithmetische Operationen sind sog. starre Funktionen (rigid), die immer
dem Residuation-Prinzip folgen
•
Eine Berechnung der folgenden Art führt zu einer Suspendierung:
nullstellen a b c | a*x*x + b*x + c =:= 0 = x
where x free
•
Andere Funktionen sind flexibel (flexible)
52
•
Der Operator ++, der zwei Listen zusammenfügt, gehört dazu, so dass die
oben definierte Funktion last nach dem Narrowing-Prinzip berechnet wird
53
Beispiel Narrowing
and True y = y
and False _ = False
or True _ = True
or False y = y
f | and (or True False) (and x True) =:= True = x
where x free
•
f führt zu True
54
Beispiel Residuation
•
Berechnung von Nullstellen
•
Prämisse: x ist Ganzzahl zwischen -20 und 20
each (x:xs) = x
each (x:xs) = each xs
generator = each [-20..20]
nullstellen a b c | a*x*x + b*x + c =:= 0
& x =:= generator = x
where x free
•
nullstellen 1 0 (-1) führt zu -1 und 1
55
5. Input/Output
•
Unter Verwendung der bisher erläuterten Sprachmittel treten keine
Seiteneffekte auf
•
Das bedeutet, dass bei Aufruf einer Funktion Objekte außerhalb der
Funktion nicht verändert werden
•
Diese Tatsache ist charakteristisch für rein funktionale Sprachen
•
Eingabe- und Ausgabefunktionen müssen daher besonders behandelt
werden, weil diese Seiteneffekte z.B. beim Schreiben einer Datei auftreten
würden
56
Monaden
•
Die Lösung des Problems wird in Curry durch die Verwendung sog.
Monaden geleistet
•
Monaden sind nicht die I/O-Operationen selbst sondern Objekte, die I/OOperationen (Aktionen) bereitstellen
•
Zur Laufzeit werden diese Aktionen dann auf die Welt außerhalb des
Programms angewandt
•
Das Problem der Seiteneffekte tritt dann außerhalb des funktionalen
Gerüsts der Sprache aus, so dass die Grundsätze der funktionalen
Programmierung weiterhin gelten können
•
Es bestehen einige Einschränkungen bezüglich der I/O-Operationen
57
•
Da Nichtterminismus in Zusammenhang mit Ein- und Ausgabe zu
Schwierigkeiten führen kann, da z.B. der Ausführungszeitpunkt der
Operationen nicht vorhersagbar ist, ist die Verwendung von I/OOperationen nicht innerhalb von Funktionen erlaubt, die nicht selbst
monadisch sind
•
Innerhalb von monadischen Funktionen müssen nichtterministische
Ausdrücke gekapselt werden, z.B. durch eine gekapselte Suchfunktion
58
6. Quicksort
•
Vergleich Curry und Java am Beispiel Quicksort
•
Vergleich von Implementierung und Ausführung in Curry und Java
•
Vergleich bzgl. Dauer der Implementierung, Anzahl der Zeilen des
Programms und Ausführungszeit
•
Gewählten Entwicklungsumgebungen
o
Java 2 Platform Standard Edition, Version 1.4.2
o
PAKCS Version 1.6.0
o
Münster Curry 0.9.6
59
Eingabe der Daten
•
Daten in Datei mit 10.000 Zufalls-Integerzahlen
•
Sortieren und Speichern in zweiter Datei
•
Lese- und Schreib-Operationen unberücksichtigt bei Vergleich der
Implementierungszeit und Code-Länge
60
Vergleich
•
Signifikante Unterschiede in alle drei betrachteten Größen
•
Implementierungszeit betrug in Curry weniger als 5 Minuten und in Java 30
Minuten.
•
Das Curry-Programm lässt sich in zwei Codezeilen (im Folgenden auf 4
Zeilen verteilt) ausdrücken, während das Java Programm 20 Zeilen
einnimmt.
61
Das Curry-Programm:
quickSort [] = []
quickSort (x:xs) = (quickSort (filter (<= x) xs)
++ [x]
++ (quickSort (filter (> x) xs)
•
Die filter-Funktion ist standardmäßig definiert und filtert eine Liste unter
Verwendung eines Prädikates
62
Ausführungszeit (Lesen, Sortieren und Schreiben)
•
PAKCS
ca. 16 Sekunden
•
Münster
ca. 1,5 Sekunden
•
Java
weniger als 1 Sekunde
63
7. Fazit
Vergleich
•
Vergleich zeigt Grundsatz deklarativer Sprachen auf
•
In deklarativen Sprachen wird das Problem und nicht dessen Lösung
betrachtet
•
Die Lösungsfindung wird dem Computer überlassen
•
Dementsprechend besteht Tendenz, dass die Implementierungszeit
geringer ist als in anderen Sprachen
•
Dies geschieht jedoch auf Kosten der Ausführungszeit
64
Anwendung
•
Bisher keine große Verbreitung
•
Beschränkt auf Hochschullehre und -forschung
Herunterladen