Clean: eine saubere Sprache

Werbung
Clean: Eine saubere Sprache
Inhaltsverzeichnis
Einführung
Eingliederung in der Sprachlandschaft
1
Grundbegriffe
2
Ersetzungsstrategie
5
Formulierung von Clean Programmen
Definition von Funktionen
7
Sorten
9
Zusammengesetzte Daten
13
Beispiele
16
Zusammenfassung
18
Version 13.01.2001
Seite 1
Einführung
Eingliederung in der Sprachlandschaft
Arten von Programmiersprachen
Zuweisungsorientiert
Variablen und Anweisungen
Sequenz von Anweisungen
Reihenfolge, Zustand
Regelorientiert (Logisch)
Fakten und Regeln
Ableitung neuer Aussagen (Versuch eine Aussage zu verifizieren)
Objektorientiert
Objekte mit innerem Zustand
Ansammlung von Objekten, die untereinander Botschaften austauschen
Funktionsorientiert
Funktionen
Auswertung der „Hauptfunktion“, in Clean „Start“ genannt, bewirkt gewöhnlich den
Aufruf anderer Funktionen
Version 13.01.2001
Seite 2
Grundbegriffe
Problemstellung: Benötigt wird eine formale Sprache
Terme erster Ordnung (Variablen sind Platzhalter für beliebige einzelne Terme)
Formale Definition:
S sei eine endliche menge, die der Sorten

F  i 0 Fi sei die Menge
der Funktionssymbole mit fester Stelligkeit und
Sortenzuordnung, dabei sei Fi die Menge der i-Stelligen Funktionssymbole. Die
Menge F0 der nullstelligen Funktionssymbole heißt auch Menge der Konstanten.
X sei eine von S und F disjunkte Menge mit Sortenzuordnung
typ sei eine Funktion die f  F und x X Sorte wie folgt zuordnet:
typx  S für x X
typc  S für c  F0
typ f  ist
der
Form
s1  ...  sn  s0
für f  Fn und
s0 ...sn  S .
s0 heißt
Ergebnissorte, s1 ...sn Argumentsorten
F einschließlich der Sortenzuweisungsfunktion heiße Signatur
Menge der Terme:
1. Jede Variable x X ist ein Term der Sorte typx 
2. Jede Konstante c  F0  F ist ein Term der Sorte typc
3. Sei f  F  F0 mit typ f   s1  ...  sn  s0 und t 0 ...t n seien Terme der Sorten s0 ...sn
dann ist f t 0 ...t n  ein Term der Sorte s0 .
4. Nichts sonst ist ein Term
Version 13.01.2001
Seite 3
Termersetzungssystem: Verarbeitungsmodell bestehend aus Terme und Regeln
zur Überführung existierender Terme in neue, äquivalente Terme.
Termgraphersetzungssystem
(Graph
Rewriting
System,
GRS):
Termersetzungssystem, in welchem Terme durch gerichtete Graphen ersetzt
werden, um durch gemeinsame Verwendung von Daten eine Vervielfachung der
Arbeit zu vermeiden.
Funktionelles Termgraphersetzungssystem (Functional Graph Rewriting System,
FGRS): Termgraphersetzungssystem das eine funktionelle Ersetzungsstrategie
verwendet.
Ersetzungsstrategie: Funktion, die angibt welcher der möglichen Terme als
nächster ersetzt (reduziert) wird.
Clean Programm: Menge von (typgebundenen) Ersetzungsregeln
Zu ersetzendes Muster (engl. redex, reducable expression): Untergraph für den es
eine Reduktionsregel mit einer passenden linken Seite gibt
Graph in Normalform: Enthält keine redex
Version 13.01.2001
Seite 4
Ersetzungsstrategie von Clean
Gegeben: Graph und eine Menge von Ersetzungsregeln (Clean: Funktionen)
Mögliche Fälle:

Mehrere redex (ersetzbare Ausdrücke) im Graphen

Mehrere Möglichkeiten ein redex zu ersetzen
Algorithmus der den nächsten redex auswählt: Ersetzungsstrategie
Jedes Termgraphersetzungssystem besitzt eine Ersetzungsstrategie
Clean: Funktionelle Strategie
Besagt dass ein Term nur dann reduziert wird wenn sein Wert benötigt wird. (Lazy
evaluation)
Betrachte einen Knoten:

Ist es ein Konstruktor (Grundsymbole die zum Aufbau von Datenstrukturen und
Ersetzungsregeln dienen) so ist der Graph in Normalform, d.h. es gibt kein redex.

Ist es ein Funktionssymbol so werden die Ersetzungsregeln in der Reihenfolge
betrachtet, in der sie im Programm angegeben sind. Die erste passende Regel
wird angewendet.
Eine Regel ist passend wenn sie die Knotenstruktur aufbewahrt, d.h. entsprechende
Knoten müssen dieselbe Signatur haben, Konstanten müssen übereinstimmen.
Ist beim Vergleich im redex ein Funktionssymbol an einer Stelle vorhanden, wo in der
Ersetzungsregel eine Konstante steht, so ruft sich der Algorithmus selber für den
Untergraphen der von diesem Funktionssymbol dargestellt wird, auf.
Die
Auswertung
von
Untergraphen
wird
beim
Versuch,
eine
passende
Ersetzungsregel zu finden, erzwungen.
Version 13.01.2001
Seite 5
Zusammenhang: Termersetzung – Funktionsorientierte Programmiersprache
Regeln des Termersetzungssystems sind die Funktionen
Ausführung jedes Clean Programms: Auswertung (Reduktion) der „Start“-Funktion.
Diese Regel muss in jedem Clean - Programm angegeben sein, nur die dafür
erforderlichen Terme werden reduziert.
Beispiel: Ein erstes Clean Programm
Start :: Int
Start = Length [3,4]
Length :: [x] -> Int
Length [a:x] = 1 + Length x
Length [] = 0
Ausführung (Reduktion) des obigen Programms (redex in fett dargestellt):
Start
Length [3,4]
1 + Length [4]
1 + 1 + Length []
1 + 1 + 0
1 + 1
2
//
//
//
//
//
//
//
a: Start ist der einzige redex
b: Dieser Graph als Ganzes ist der neue redex
c: + erzwingt die Reduktion seines Zweiten Parameters
d: das gleiche noch ein mal
e: zweiter Parameter von + durch Normalform ersetzt
f: Der ganze Graph wird erneut zum redex
g :Graph hat die Normalform erreicht
Graphische Darstellung:
Bemerkung: Ist der Graph ein Baum so gibt es keinen Unterschied zu einem
Termersetzungssystem
Version 13.01.2001
Seite 6
Formulierung von Clean Programmen
Definition von Funktionen
Regeln zur Ersetzung der Terme

Kombination: Verwenden bereits definierter Funktionen
over n k = fac n / (fac k * fac (n-k))
roots a b c =
[ (~b+sqrt(b*b-4.0*a*c)) / (2.0*a)
, (~b-sqrt(b*b-4.0*a*c)) / (2.0*a)
]
Konstanten: Funktionen ohne Argument
pi = 3.1415926535
e = exp 1.0

Fallunterscheidung:
signum x
| x>0
| x==0

= 1
= 0
= -1
Muster: Partielle Funktionen, Einschränkung des Definitionsbereichs
Die Funktion
h [1,x,y] = x+y
ist nur für Listen bestehend aus 3 Elementen, von denen das erste 1 ist, definiert.
Folgende Funktion zählt die Elemente einer Liste
reverse :: [a] -> [a]
reverse [] = []
reverse [x:xs] = reverse xs ++ [x]

Rekursion:
length [] = 0
length [_:rest] = 1 + length rest
’_’ kann als Platzhalter für nichtverwendete Teile des Musters stehen
Version 13.01.2001
Seite 7
Lokale Definitionen können durch zwei Konstrukte erstellt werden:
roots a b c =
let
s = sqrt (b*b-4.0*a*c)
d = 2.0*a
in [(~b+s)/d , (~b-s)/d ]
und
f x y = g(x+w)
where
g u = u+v
where
v = u*u
w = 2+y
Einrückung gibt Aufschluss über den Gültigkeitsbereich zu dem eine Definition
gehört, solange man keine geschweiften Klammern verwendet und so die
Gültigkeitsbereiche explizit angibt.
f x y = g (x+w)
where { g u =u + v
where { v = u * u
};
w = 2 + y
};
Diese zwei Darstellungsarten dürfen jedoch auf Modulebene nicht gemeinsam
auftreten.
Version 13.01.2001
Seite 8
Sorten
Jeder Ausdruck ist von einer bestimmten Sorte, z.B.:
Start :: Int
Start = 3+4
:: heißt “ist von der Sorte”

Es gibt 4 Grundsorten: Int, Real, Bool, Char

Wenn S eine Sorte ist dann ist auch [S] eine Sorte, die der Listen über Elemente
der Sorte S
x :: [Int]
x = [1,2,3]
//Liste von Int
z :: [[Bool]]
//Liste von Listen von Bools
z = [[True,True],[False,True,False]]
Sortenangaben sind für Funktionen mit einem oder mehreren Parameter möglich:
sum :: [Int] -> Int
//Funktion die eine Liste von Ints als Parameter erhält
//und ein Int zurückliefert
roots :: Real Real Real -> [Real]
trigs :: [Real->Real]
trigs = [sin,cos,tan]
//Funktion die 3 Real Werte als Parameter
//erhält und eine Liste davon zurückliefert
//Liste von Funktionen die Real in Real überführen
Sortenangaben sind nicht verpflichtend, Verwendung erhöht aber Lesbarkeit des
Codes und verkleinert die Fehleranfälligkeit
Automatische Sortenbestimmung:
g
g
|
|
0 y z = y
x y z
x == y = y
otherwise = z
Festlegungen:
1) typ(0) = Int
//erster Parameter ist 0
2) typ(y) = rückgabetyp(g)
//y wird zurückgeliefert
3) typ(x) = typ(y)
//x wird mit y verglichen
Schlussfolgerungen:
4) aus 1) und 3) => typ(y) = Int
5) aus 4) und 2) => rückgabetyp(g) = Int
6) da z zurückgeliefert wird und 5) => typ(z) = Int
Version 13.01.2001
Seite 9
g :: Int Int Int -> Int
Version 13.01.2001
Seite 10
Polymorphismus: Sortenvariablen
length :: [a] -> Int
//Anzahl der Elemente einer Liste von ‚irgendetwas’
hd :: [a] -> a
//Das Erste Element einer Liste
id :: a -> a
id x = x
//Identitätsfunktion
Überladen von Funktionen
Im Allgemeinen ist es nicht möglich denselben Namen für zwei Funktionen zu
verwenden, dazu gibt es in Clean Funktionsklassen:
Die Definition
class (+) infixl 6 a :: a a -> a
vereinbart die Klasse der Funktionen die mit + bezeichnet werden, in Infixnotation
verwendet werden dürfen und dabei linksbindend wirken, und Priorität 6 haben.
Konkrete Implementierung: Instanz
instance + Bool
where
(+) :: Bool Bool -> Bool
(+) True b = True
(+) a b = b
Einschränkung der Sorten:
double :: a -> a | + a
gibt an dass double für alle Sorten definiert ist für denen die + Funktion definiert ist,
double wird dabei zur überladenen Funktion
Version 13.01.2001
Seite 11
Spezialformen infix, infixl, infixr ermöglichen das Erstellen von Funktionen die in
Infixnotation verwendet werden dürfen:
(&&) infixr 1 :: Bool Bool -> Bool
dabei gibt 1 die Priorität des Operators an.
In den Standardbibliotheken sind die Prioritäten wie folgt definiert:
11: reserviert für Auswahloperatoren von 5: ++
+++
Arrays und Records
10: reserviert für Funktionsaufrufe
4: ==
9: o
3: &&
! %
8: ^
<> < > >= <=
2: ||
7: *
/ mod rem
1: :=
6: +
- bitor bitand bitxor
0: ‘bind’
Angabe der Priorität: Klammerersparnis
Was passiert wenn ein Operator im selben Ausdruck mehrmals hintereinander
verwendet wird? Beispiele aus dem
class (^) infixr 8 a :: !a !a
-> a
//wirkt rechtsbindend
2 ^ 2 ^ 3  2 ^ (2 ^ 3)
class (+) infixl 6 a :: !a !a -> a
//wirkt linksbindend
2 + 3 + 4  (2 + 3) + 4
class (/) infix 7 a :: !a !a -> a
//darf nicht mehrmals hintereinander in einem
//Ausdruck vorkommen
64 / 8 / 2 ist ungültig
Version 13.01.2001
Seite 12
Funktionen höherer Ordnung
Funktionen als Rückgabewert von Funktionen
Partielle Parametrisierung – Currying
In Clean ist die Verwendung einer Funktion mit weniger Parameter als
Vorgeschrieben möglich:
successor :: (Int -> Int)
successor = plus 1
successor ist von der Sorte‚ Funktion die ein Int in einem Int überführt’
Die Funktionen
plus :: Int Int -> Int
plus a b = a + b
//liefert direkt ein Int
und
plus :: Int -> (Int -> Int)
plus a = (+) a
//liefert eine einstellige Funktion die Int liefert
sind bis auf ihrer Stelligkeit äquivalent.
Funktionen als Parameter
twice führt eine Funktion 2 mal hintereinander aus.
twice :: (t->t) t -> t
twice f x = f (f x)
twice
inc
inc
inc
1+1
2
inc 0
(inc 0)
(0+1)
1
twice twice inc 0
twice (twice inc) 0
(twice inc) ((twice inc) 0)
* (twice inc) 2 // wie daneben
inc (inc 2)
* inc 3
* 4
Andere Beispiele:
map :: (a -> b) [a] -> [b]
until :: (a->Bool) (a->a) a -> a
Die Lambda Notation: Führt eine lokale, anonyme Funktion ein:
\ Muster -> Ausdruck
Beispiel aus der Standardbibliothek von Clean:
Operator o zur Hintereinanderausführung von Funktionen
(o) infixr 9 :: (b -> c) (a -> b) -> (a -> c)
(o) g f = \x -> g (f x)
Version 13.01.2001
Seite 13
Zusammengesetzte Daten
Listen
Sind in Clean Anreihungen von Elementen derselben Sorte

Konstruktion durch Aufzahlung der Elemente
[1,3,7,2,8] :: [Int]
[3<4,a==5,p && q] :: [Bool]

Konstruktion mit dem : Operator (vgl. Scheme: cons)
Spiegelt interne Darstellung der Listen
xs = [1,2,3]

ist eigentlich eine Abgekürzte Schreibweise für xs
= [1:[2:[3:[]]]]
Konstruktion durch Aufzählung von Intervallen mit der .. Notation
xs = [1..9]
xs = [1,3..20]
//Sonderfall: Intervallänge 1
’..’ Notation wird vom Compiler durch die from_then_to Funktion ersetzt
from_then_to : a a a -> [a] | Enum a
from_then_to n1 n2 e
| n1 <= n2 = _from_by_to n1 (n2-n1) e
| otherwise = _from_by_down_to n1 (n2-n1) e
where
from_by_to n s e
| n <= e = [n : _from_by_to (n+s) s e]
| otherwise = []
from_by_down_to n s e
| n >= e = [n : _from_by_down_to (n+s) s e]
| otherwise = []

Konstruktion durch Angabe einer charakteristischen Eigenschaft
Aus der Mengenlehre bekannt:


V  x 2 x  N , x mod 2  0
Clean kennt eine ähnliche Konstruktionsweise für Listen:
lst :: [Int]
lst = [x*x \\ x <- [1..10] | x mod 2 == 0]
Beispiel: Quicksort unter Verwendung dieses Konstrukts:
qsort :: [a] -> [a] | Ord a //für Sorte a muss der Vergleichsoperator existieren
qsort [] = []
Version 13.01.2001
Seite 14
qsort [a:xs] = qsort [x \\ x<-xs | x<a] ++ [a] ++ qsort [x \\ x<-xs | x>=a]
Version 13.01.2001
Seite 15
Unendliche Listen – Lazy Evaluation
Die Auswertungsreihenfolge (Reduktionsstrategie) mit der Clean arbeitet erlaubt es,
potentiell unendlich lange Listen als Zwischenergebnisse zu verwenden.
Natürlich hält ein Programm nicht, wenn die Reduktion einer unendlich langen Liste
erzwingt wird.
Beispiel: Erstellen eine Liste aller natürlichen Zahlen n >= 1 mit 3^n < 5
takeWhile ((>) 5) (map ((^)) 3) [1..])
takeWhile ((>) 5) (map ((^) 3) [1:[2..]])
takeWhile ((>) 5) [(^) 3 1:map ((^) 3) [2..]]
takeWhile ((>) 5) [3:map ((^) 3) [2..]]
[3:takeWhile ((>) 5) (map ((^) 3) [2..])]
//3 < 5
[3:takeWhile ((>) 5) (map ((^) 3) [2:[3..]])]
[3:takeWhile ((>) 5) [(^) 3 2: map ((^) 3) [3..]]]
[3:takeWhile ((>) 5) [9: map ((^) 3) [3..]]]
//9 > 5
[3:[]]
Anderes Beispiel:
Unendlich lange Liste der Primzahlen nach dem Algorithmus von Eratosthenes
primes :: [Int]
primes = sieve [2..]
sieve :: [Int] -> [Int]
sieve [prime : rest] = [prime : sieve[i \\ i <- rest | i mod prime <> 0]]
Um sich den Anfang dieser Liste anzusehen, kann man z.B. die takeWhile Funktion
verwenden:
Start = takeWhile ((>) 100) primes
Lazy evaluation ist nicht nur bei unendlich langen Listen vorteilhaft:
prime :: Int -> Bool
prime x = divisors x == [1,x]
//divisors liefert Liste der Teiler
Eine Applikative Auswertungsreihenfolge hätte als Folge die Erzeugung der Liste
aller Teiler von x.
Mit der Lazy evaluation von Clean werden z.B. für x = 30 nur die ersten 2 Elemente
der Liste divisors x berechnet, dieses reicht dem == Operator um festzustellen dass
die beiden Listen ungleich sind.
(==) [x:xs] [y:ys] = x==y && xs==ys //rekursive Zeile des Vergleichsoperators
(&&) False x = False
(&&) True x = x
Version 13.01.2001
//Logischer UND Operator: wenn das erste Argument
//False ist, wird x nicht ausgewertet
Seite 16
Tupel
Anreihungen mit fester Länge von Elementen verschiedener Sorten
(1,'a') :: (Int,Char)
("foo",True,2) :: (String,Bool,Int)
([1,2],sqrt) :: ([Int],Real->Real)
(1,(2,3)) :: (Int,(Int,Int))
Können zur Sortenbildung und in Muster verwendet werden
Sind bei Funktionen die mehr als ein Rückgabewert haben sollen, nützlich
Beispiel aus der Standardbibliothek: Aufteilung von Listen
splitAt :: Int [a] -> ([a],[a]) //Liefert ein 2-Tupel von Listen
splitAt 0 xs = ([] ,xs)
splitAt n [] = ([] ,[])
splitAt n [x:xs] = ([x:ys],zs)
where
(ys,zs) = splitAt (n-1) xs
Records
Unterschied zu Tupeln: Jedes Element wird durch einen Namen eindeutig bestimmt.
//Vereinbaren
:: Person = {
,
,
}
der neuen Sorte
name :: String
birthdate :: (Int,Int,Int)
cleanuser :: Bool
//Erstellen eines Records von der Sorte Person
SomePerson :: Person
SomePerson = { name = "Rinus"
, birthdate = (10,26,1952)
, cleanuser = True
}
Arrays
Anreihungen mit fester Länge von Elementen derselben Sorte
Sehr effizient, da die Zugriffszeit auf ein Element konstant ist
//Vereinbarung
MyArray :: {Int}
MyArray = {1,3,5,7,9}
//Auswählen des 2-ten Elements
MyArray.[2]
Arten von Arrays:
’lazy array’ : Speicherblock mit Zeigern auf Elemente, diese Werden nur bei Bedarf
ausgewertet (reduziert).
Notation: {Sorte}
’strict array’ : wie oben, jedoch werden alle Elemente ausgewertet
Notation: {!Sorte}
’unboxed array’ : Speicherblock enthält keine Zeiger, sondern direkt Elemente
Notation: {#Grundsorte}, wobei Grundsorte eines von Bool Int Real Char ist.
Version 13.01.2001
Seite 17
Beispiele
Gemeinsame Verwendung von Zwischenergebnisse (Untergraphen)
Start = 3 * 7 + 3 * 7
Start = x + x where x = 3 * 7
Start
 3 * 7 + 3 * 7
 3 * 7 + 21
 21 + 21
 42
Start
 x + x where x = 3 * 7
 x + x where x = 21
 42
power :: Int Int -> Int
power x 0 = 1
power x n = x * power x (n-1)
Start :: Int
Start = power (3+4) 2
Start
power (3+4) 2
x * power x (2-1) where x = 3+4
x * power x 1 where x = 3+4
x * x * power x (1-1) where x = 3+4
x * x * power x 0 where x = 3+4
x * x * 1 where x = 3+4
x * x * 1 where x = 7
x * 7 where x = 7
49
Version 13.01.2001
Seite 18
Quicksort – optimierte Fassung
Erste Fassung von Quicksort: viel zu großer Aufwand wegen ++ Operator
(Listenkonkatenation)
qsort :: [a] -> [a] | Ord a //für Sorte a muss der Vergleichsoperator existieren
qsort [] = []
qsort [a:xs] = qsort [x \\ x<-xs | x<a] ++ [a] ++ qsort [x \\ x<-xs | x>=a]
Zweite Fassung mit separater Ergebnisliste:
qsort2 :: [a] -> [a] | Ord a
qsort2 l = qs l []
qs :: [a] [a] ->[a] | Ord a
qs [] c = c
qs [a:xs] c = qs [x \\ x<-xs | x<a] [a:qs [x \\ x<-xs | x>=a] c]
Start :: [Int]
Start = qsort2 [1,2,1]
Start
qsort2 [1,2,1]
qs [1,2,1] []
qs [] [1:qs [2,1] []]
[1:qs [2,1] []]
[1:qs [1] [2:qs [] []]]
[1:qs [] [1:qs [] [2:qs [] []]]]
[1:[1:qs [] [2:qs [] []]]]
[1:[1:[2:qs [] []]]]
[1:[1:[2:[]]]]
= [1,1,2]

Version 13.01.2001
Seite 19

Zusammenfassung
Clean
Funktionsorientierte Programmiersprache
Basiert auf Termgraphersetzungssysteme
- Ersetzungsstrategie: Lazy Evaluation
Typgebunden (Sorten)
Literatur:



T.H.Brus: CLEAN – A language for functional graph rewriting
in: Proc. Conference on Functional Programming Languages and Computer Architecture, Lecture Notes Computer Science 274 (1987) p. 364-384
Functional Programming in Clean, draft, july 1999
Reinhard Bündgen: Termersetzungssysteme, Vieweg Verlag, 1998
Version 13.01.2001
Seite 20
Herunterladen