Funktional-logische Programmierung in Maude

Werbung
Arbeitsgruppe Programmiersprachen und Übersetzerkonstruktion
Institut für Informatik
Christian-Albrechts-Universität zu Kiel
Betreuer: Prof. Dr. Michael Hanus
Seminararbeit
Funktional-logische Programmierung in
Maude
Jasper Paul Sikorra
Wintersemester 2015/2016
Inhaltsverzeichnis
1
2
3
4
5
Einleitung
1
Maude
2
2.1
Funktionale Module
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
2.2
Systemmodule . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
Full-Maude
7
3.1
Full-Maude als Erweiterung von Maude
. . . . . . . . . . . . . . . . . . .
3.2
Narrowing in Full-Maude
7
. . . . . . . . . . . . . . . . . . . . . . . . . . .
7
3.2.1
Narrowing als Generalisierung von Termersetzung . . . . . . . . . .
7
3.2.2
Die Narrowing Implementierung in Full-Maude
7
. . . . . . . . . . .
Funktional-logische Programmierung in Full-Maude
9
4.1
Nicht-deterministische Berechnungen . . . . . . . . . . . . . . . . . . . . .
9
4.2
Gleichheitsconstraint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
4.3
Freie Variablen
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
4.4
Residuation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
Zusammenfassung
15
ii
1 Einleitung
Funktional-logische Sprachen kombinieren zwei Programmierparadigmen: die logische
Programmierung und die funktionale Programmierung. Daraus entstehen besondere Eigenschaften, die funktional-logischen Programmiersprache zu eigen sind.
In
Full-Maude,
der Erweiterung der Sprache
Maude,
können diese Eigenschaften repro-
duziert werden.
Maude
ist eine Programmier- und Spezikationssprache deren Programme aus Gleichun-
Rewrite -Regeln bestehen, welche durch Termersetzung ausgewertet werden. In
der Erweiterung von Maude namens Full-Maude ist Narrowing implementieren. Narrowing kann eingesetzt werden, um Lösungen für das Problem zu nden, ob eine Termersetzung für einen Term mit freien Variablen durchgeführt werden darf. Narrowing kann
gen und
auch genutzt werden, um Instanzen für freie Variablen zu generieren.
Wie oben beschrieben sind die Sprache
Maude
und die Erweiterung
Full-Maude
geeignet,
typische funktional-logische Programmeigenschaften zu implementieren. Das bedeutete
nicht-deterministische Programmierung, das Ausnutzen von
auf ungebunden Variablen lassen sich in
Im Folgenden wird zunächst
Maude
Full-Maude
Constraints
und das Suchen
umsetzen[Esc14].
vorgestellt, wobei die Unterscheidung zwischen funk-
tionalen und Systemmodulen eingegangen und die Ausführung von Programmen kurz
erklärt wird. Dann wird
Full-Maude
und vor allem
Narrowing
in
Full-Maude
beschrie-
ben. Schlieÿlich werden besondere Features der funktional-logischen Programmierung,
wie nicht-deterministische Berechnungen und die Auswertung von Ausdrücken mit freien
Variablen vorgestellt und gezeigt wie diese in
1
Full-Maude
reproduziert werden können.
2 Maude
Maude
ist eine höhere Programmier- und Spezikationssprache, welche auf Termerset-
zung basiert.
Maude -Programme
sind in Module aufgeteilt, welche aus einer Signatur,
Gleichungen und Termersetzungsregeln bestehen. Gleichungen und Termersetzungsregeln haben eine einfache semantische Bedeutung, nämlich die Ersetzung von Instanzen
der linken Seite durch passende Instanzen der rechten Seite.
Besteht ein
Maude -Modul
nur aus einer Signatur und Gleichungen, wird es funktional
genannt und wird durch Vereinfachung der Gleichungen ausgewertet. Enthält es auch
Termersetzungsregeln so heiÿt es Systemmodul und wird durch Übergänge zwischen Zuständen ausgewertet. Diese Übergänge stellen die Anwendung der Termersetzungsregeln
von links nach rechts dar.
Auf diese Weise erlaubt es Maude sowohl deterministische, als auch nebenläuge, nichtdeterministische Berechnungen auszuführen und enthält einen funktionalen Teil, welcher
eigenständig genutzt werden kann.
2.1 Funktionale Module
Ein funktionales Modul in
Maude
besteht aus Signaturen, welche Datentypen und Ope-
rationen deklarieren, und Gleichungen, welche die Operationen denieren. Ein rein funktionales Modul zeichnet sich durch seine Schlüsselwörter aus und besitzt die Struktur:
fmod <
ModuleName>
DeclarationsAndStatements>
is <
endfm
Ein einfaches Beispiel für ein funktionales Modul ist die Denition der Addition auf den
Peano-Zahlen mittels Gleichungen:
SIMPLE−NAT is
Nat .
op zero : −> Nat [ctor].
op s_ : Nat −> Nat [ctor].
op _+_ : Nat Nat −> Nat [comm].
vars N M : Nat .
eq zero + N = N .
eq s N + M = s (N + M) .
fmod
sort
endfm
In diesem Beispiel wird eine einfache Algebra über den natürlichen Zahlen beschrieben. In
der Signatur werden die Sorte
das Schlüsselwort
ctor)
Nat und ihre beiden Konstruktoren (gekennzeichnet durch
+ auf der Sorte Nat
deklariert. Auÿerdem wird der Operator
deklariert und deniert, dass dieser kommutativ ist (gekennzeichnet durch das Schlüsselwort
comm). Der Operator wird dann im Sinne der Addition auf den Peano-Zahlen durch
2
2 Maude
Gleichungen deniert.
In
Maude
können neben Sorten auch Untersorten deklariert werden. Beispielsweise könn-
NzNat
ten wir eine Untersorte
deklarieren, welche sämtliche natürliche Zahlen enthalten
soll, die nicht Null sind:
subsort
NzNat
<
Nat
.
Die Operatoren könnten dann entsprechend angepasst werden:
op
op
op
op
zero : −> Nat [ctor].
s_ : Nat −> NzNat [ctor].
_+_ : Nat Nat −> Nat [comm].
_+_ : NzNat Nat −> NzNat [comm]
.
Eine Berechnung in einem funktionalen Modul ist die Vereinfachung von Termen mit
Hilfe von Gleichungen. Bei einem Vereinfachungsschritt für einen Term wird nach einer Belegung für die Variablen in einer linken Gleichungsseite gesucht, durch die der
Term und die Gleichungsseite identisch werden. Der Term wird dann durch die rechte
Gleichungsseite unter dieser Belegung ersetzt.
Werden solche Vereinfachungsschritte wiederholt, bis keine linke Gleichungsseite mehr
angewendet werden kann, so erhält man die kanonische Form des Terms. In einem funktionalen Modul sollte diese für jeden Ausdruck erreichbar sein und nicht von der Auswahl
der Gleichungen in den Vereinfachungsschritten abhängen. Das heiÿt ein funktionales
1
Modul sollte konuent und terminierend sein.
Erhält man sämtliche kanonische Formen aus Termen, die aus Konstruktoren und Operatoren gebildet werden, so bilden diese zusammen mit den Operatoren genau die im
Programm beschriebene Algebra. In unserem Beispiel sind diese kanonischen Formen genau die Menge der Peano-Zahlen
Im
Maude
{zero, s zero, s s zero, ...}.
Interpreter kann eine Vereinfachung durch das Schlüsselwort
reduce
eingelei-
tet werden:
Maude> load simple−nat.maude
Maude> reduce s (s (zero)) + s (zero) .
reduce in SIMPLE−NAT : s s zero + s zero .
rewrites: 3 in 0ms cpu (0ms real) (~ rewrites/second)
result Nat: s s s zero
Hierbei wird der Term so lange vereinfacht, bis keine Gleichung mehr anwendbar ist.
Gleichungen können in
Maude
mit dem Schlüsselwort
noexec
versehen werden, was da-
zu führt, dass sie nicht für Vereinfachungen verwendet werden und somit zunächst nur
der Spezikation dienen. Weiterhin können Operatoren mit Attributen wie
comm
verse-
hen werden, um auf bestimmte mathematische Eigenschaften hinzuweisen. Assoziativität
und Kommutativität sollte in
Maude
zum Beispiel nur durch dieses Schlüsselwort ausge-
drückt werden, da sonst nicht-terminierenden Vereinfachungen entstehen können.
1
mehr in [Cla+15], Kapitel 4.7
3
2 Maude
Maude
kennt neben Sorten auch noch
kinds. Werden zusammenhängende Komponenten
von Sorten aus der Ordnungsrelation auf den Sorten gebildet, so hat jede dieser Kom-
kind. Jeder Term hat somit auch einen zugeordneten kind.
kind, so handelt es sich um einen Error-Term. Dabei
werden kinds implizit durch Maude bereitgestellt. Denieren wir zum Beispiel
ponenten einen zugeordneten
Hat ein Term keine Sorte, aber einen
op
p_ : NzNat −> NzNat
dann wird dem Term
p zero der kind [NzNat] zugeordnet, jedoch keine Sorte. kinds
kön-
nen auch explizit in der Deklaration von Operatoren benutzt werden. Werden Argumente
als
kinds
deklariert, so gilt die Funktion als deniert für alle Argumente, deren Resultat
bei der Funktionsauswertung eine Sorte haben, für alle anderen Argumente liefert die
Funktionsauswertung nur einen
Error-Term.
2.2 Systemmodule
Systemmodule unterscheiden sich grundlegend von funktionalen Modulen. In Systemmodulen werden, zusätzlich zu der Signatur und Gleichungen, Termersetzungsregeln festgelegt, welche ähnlich wie die Gleichungen bei der Vereinfachung immer von links nach
rechts angewendet werden. Die Termersetzungsregeln müssen allerdings weder ein konuentes noch ein terminierendes System bilden. Bei einem Termersetzungsschritt werden
durch Maude zufällige Regeln, deren linke Seite instantiiert werden kann, ausgewählt,
um einen Teilterm des Terms umzuschreiben. Das
Rewriting
ist also im Allgemeinen
nicht-deterministisch.
Das folgende Modul deniert die additive Zerlegung in die Zahlen eins, zwei und drei mit
Hilfe von drei Rewrite-Regeln:
mod
DECOMPOSITION is
protecting SIMPLE−NAT .
sort NatList .
subsort Nat < NatList .
op _|_ : NatList NatList −> NatList [comm assoc ctor]
.
decomposite1] : s s X:Nat => s(zero) | s X:Nat .
decomposite2] : s s s X:Nat => s(s(zero)) | s X:Nat .
[decomposite3] : s s s s X:Nat => s(s(s(zero))) | s X:Nat
rl [
rl [
rl
.
endm
Das Modul wird mit
mod ... is ... endm
deklariert, was darauf hinweist, dass es sich um ein Systemmodul handelt. Am Anfang des
Moduls wird das funktionale Modul
SIMPLE-NAT importiert, welches im letzten Abschnitt
vorgestellt wurde.
Dann wird die Sorte der Listen über den natürlichen Zahlen deklariert. Einzelne natürliche Zahlen sollen auch zu der Sorte
NatList
Untersorte deniert.
4
gehören, entsprechend wird
Nat
als
2 Maude
_|_ deklariert, welcher zwei Listen konkateniert. Die in
Dann wird ein Listenkonstruktor
diesem Modul denierte Version der Verkettung von Listen ist assoziativ und kommutativ, dass heiÿt die Gleichheit von Listen hängt nicht von der Reihenfolge der Elemente
2
oder der Listenkonkatenation ab.
Es werden dann drei Regeln für die additive Zerlegung natürlicher Zahlen in Listen der
Rewrite -Regeln kann nun Rewriting
modulo Gleichungen angewendet werden. Beim Rewriting modulo Gleichungen im Sinne
von Maude wird der Term zunächst mit Hilfe der Gleichungen auf seine kanonische Form
gebracht und dann ein Teilterm mit einer passenden Rewrite -Regel durch einen anderen
Term ersetzt. Das nden einer passende Rewrite -Regel für den Teilterm erfolgt dabei auf
Zahlen Eins, Zwei und Drei deniert. Mit Hilfe dieser
die selbe Art wie bei Gleichungen. Das heiÿt es wird eine Belegung für eine linke Regelseite gesucht, die zu einer Identität von Teilterm und Regelseite führt, und der Teilterm
dann durch die rechte Regelseite unter der selben Belegung ersetzt.
search-Befehl können in Maude nun mögliche Rewrite-Schritte um einen Term
t0 umzuschreiben, gesucht werden. Dabei können Anzahl der RewriteSchritte, Tiefe der Suche und Art der Relation (=>*, =>+, =>!, etc.3 ) ausgewählt werden.
Dabei werden in t im Gegensatz zum Narrowing allerdings keine Variablen neu instantiiert. Mit search können zum Beispiel alle Zerlegungen der Zahl Drei angezeigt werden:
Mit dem
t
in einen Term
Maude> search
in
DECOMPOSITION : s s s zero
Solution 1 (state 0)
states: 1 rewrites: 0 in 0ms cpu
Ns:NatList −−> s s s zero
=>
* Ns:NatList
.
ms real)
(~
rewrites/second)
Solution 2 (state 1)
states: 2 rewrites: 1 in 0ms cpu (0ms real)
Ns:NatList −−> s zero | s s zero
(~
rewrites/second)
Solution 3 (state 3)
states: 4 rewrites: 4 in 0ms cpu (0ms real)
Ns:NatList −−> s zero | s zero | s zero
(~
rewrites/second)
(0
No more solutions.
Es kann auch nach möglichen Teilzerlegungen gesucht werden:
Maude> search
in
DECOMPOSITION : s s s X:Nat
Solution 1 (state 2)
states: 3 rewrites: 2 in 0ms cpu
Ns:NatList −−> s X:Nat
ms real)
(0
(~
=>
* Ns:NatList | s s zero
rewrites/second)
No more solutions.
2
3
Es handelt sich hierbei also eher um Multimengen statt Listen.
Eine ausführliche Beschreibung ndet sich in [Cla+15]
5
.
2 Maude
Würde die in diesem Fall ungebundene Variable
X:Nat
mit Peano-Zahlen instantiiert
werden, so würden noch weitere Lösungen hinzukommen. Dies ist in
Erweiterung allerdings nicht möglich.
6
Maude
ohne die
3 Full-Maude
3.1 Full-Maude als Erweiterung von Maude
In
Maude
wurde von Anfang an eine Schnittstelle für Metaprogrammierung vorgese-
hen. Metaprogrammierung meint dabei die Umprogrammierung der Programmiersprache
selbst in der Programmiersprache. Eine Einsatzmöglichkeit von Metaprogrammierung in
Maude ist die Erweiterung von Maude selbst. Dies wurde in der Spracherweiterung FullMaude umgesetzt.
Full-Maude enthält sämtliche Features von Maude, sowie verschiedene Erweiterungen
wie eine Notation für objekt-orientierte Programmierung und ein generalisierte Form der
Termersetzung namens
Narrowing. Narrowing ist für die funktional-logische Programmie-
rung von groÿer Bedeutung, da es die Auswertung von Ausdrücken mit freien Variablen
erlaubt.
3.2 Narrowing in Full-Maude
3.2.1 Narrowing als Generalisierung von Termersetzung
In 2.2 wurde
Rewriting
mittels Termersetzungsregeln erläutert.
Rewriting
mit
Narrowing
verallgemeinert dieses. Der Hauptunterschied besteht darin, dass bei dem Suchen nach
Belegungen, um eine Identität zwischen Term und linker Regelseite herzustellen, auch
freie Variablen in dem Term mit Belegungen versehen werden können.
Dies erlaubt deutlich erweiterte Programmierung mit freien Variablen. So kann zum
Beispiel die Frage, ob ein Term mit freien Variablen mit einer Instantiierung dieser in
einen anderen Term umgeschrieben werden kann, durch eine Ausführung von
mit
Narrowing
Rewriting
beantwortet werden.
3.2.2 Die Narrowing Implementierung in Full-Maude
In
Full-Maude
existiert eine Narrowing-Implementierung von Santiago Escobar. Betrach-
ten wir wieder als Beispiel die Addition, diesmal mit
und der Implementierung der natürlichen Zahlen aus
mod
NATRL
is
_add_ : Nat Nat −> Nat .
0 add M:Nat => M:Nat .
s M:Nat add N:Nat => s (M:Nat
op
rl
rl
+
N:Nat)
endm
7
.
Rewrite -Regeln
Maude selbst:
statt Gleichungen
3 Full-Maude
X add 0 wäre dann X add 0 {X→0, add1} 0. Es würde
also eine Belegung für die freie Variable X gefunden werden, unter der eine Rewrite-Regel
Ein Narrowing-Schritt für den Term
angewendet werden kann. Das Resultat ist dann die rechte Seite dieser
Rewrite-Regel
mit
der selben Belegung.
Ein anderer
Narrowing-Schritt
Es lässt sich das
wäre
search-Kommando
X add 0
{X→s Y,add2}
s (Y add 0).
nun auch mit Narrowing benutzen, verwenden wir
wieder das Beispiel aus 2.2, so erhalten wir mehr Lösungen.
Maude> (search
[2] in
DECOMPOSITION : s s s X:Nat
~>
* Ns:NatList | s s zero
.)
Solution 1
Ns:NatList −−> s X:Nat
Solution 2
Ns:NatList −−> s s s zero
X:Nat −−> s s zero
;
No more solutions.
Die Anzahl der Lösungen wurde in diesem Fall auf zwei beschränkt. Bei der zweiten
Lösung erfolgt die Instantiierung der Variable
wir im Beispiel in 2.2 nicht erhalten.
8
X
durch Narrowing. Diese Lösung haben
4 Funktional-logische Programmierung
in Full-Maude
Funktional-logische Programmierung ist eine Form der deklarativen Programmierung.
Dabei werden wichtige Paradigmen der funktionalen und der logischen Programmierung
vereint. Funktionale Programme bestehen aus Daten und Funktionen, ihre Semantik
entspricht Funktionsauswertungen. Logische Programme bestehen aus Fakten und Regeln
aus denen mit Hilfe von Inferenzsystemen Schlüsse berechnet werden können. Logische
Programme können auch mit freien Variablen ausgewertet werden, indem nach passenden
Werten gesucht wird.
Die besonderen Vorteile funktionaler Sprachen, wie die Möglichkeit der Bedarfsauswertung, von polymorphen Typen und Funktionen höherer Ordnung werden in funktionallogischen Programmiersprachen mit den Vorteilen der logischen Programmierung, wie
Berechnungen mit partiellen Informationen und Constraint-Solving, kombiniert.
Aus mathematischer Sicht basieren funktionale Programmiersprachen auf dem
λ-Kalkül,
während logische auf der Prädikatenlogik erster Stufe aufbauen. Bei der Vereinigung dieser beiden grundverschiedenen Systeme kann
Maude
Narrowing
1
eingesetzt werden .
bietet oensichtlich Möglichkeiten der rein funktionalen Programmierung, was
durch die Aufteilung in funktionale und Systemmodule deutlich wird. Die nicht-deterministische
Auswertung ist in durch das nicht-deterministische
Rewriting
vorhanden.
Im Folgenden wird daher auf die Reproduktion der besonderen Eigenschaften des NichtDeterminismus in funktional-logischen Programmiersprachen und das Rechnen mit freien
Variablen und Constraints eingegangen.
4.1 Nicht-deterministische Berechnungen
Nicht-Determinismus bedeutet, dass ein Ausdruck zu unterschiedlichen Ergebnissen ausgewertet werden kann. In
?
Zahl die Konstante
coin = 0
Maude
In
rl
1
?
ist es möglich Nicht-Determinismus durch den Operator
coin
ausgewertet wird, möglich sind
0
und
1.
1
würde man diesen Nicht-Determinismus durch
coin : −> Nat
coin => 0 .
coin => 1 .
op
rl
Curry
auszudrücken. In dem folgenden Programm kann nicht zugesichert werden, zu welcher
.
Vergleiche [Han13]
9
Rewrite -Regeln ausdrücken:
4 Funktional-logische Programmierung in Full-Maude
Werden in
Curry
nicht-deterministische Parameter an deterministische Funktionen über-
geben, so kann das Ergebnis von der Auswertungsstrategie abhängen. Nehmen wir etwa
das
Curry -Programm
Int −> Int
double
::
double
x = x + x
welches eine einfache Funktion deniert. Bei der Auswertung von
double coin
vari-
iert nun die Menge der möglichen Ergebnisse je nach Auswertungszeitpunkt des nichtdeterministischen Arguments. Wird das Argument vor der Funktionsauswertung ausgewertet (
call-time choice ),
so ist die Menge der möglichen Ergebnisse des Aufrufs
{0, 2}.
Wird das Argument ausgewertet, wenn es während der Funktionsauswertung aufgerufen
wird (
run-time choice ), so erhalten wir {0, 1, 2} als mögliche Ergebnisse von double coin.
Ähnliche Ergebnisse können auch in Maude erzielt werden. Dafür deklarieren wir
und denieren passende
Rewrite -Regeln:
double : Nat −> Nat .
double(N:Nat) => N:Nat
double
op
rl
Führen wir nun mit
+
N:Nat
.
Maude ein search aus, so erhalten wir drei Ergebnisse für double(coin):
Maude> search in NATLR : double(coin)
N:Nat −−> 0
N:Nat −−> s 0
N:Nat −−> s s 0
No more solutions.
In diesem Fall werden in
Ergebnismenge
time choice
Maude
{0, 1, 2}
noch um eine
=>!
N:Nat
.
alle möglichen
Rewrite -Schritte
probiert, was zu der
führt. Hierbei handelt es sich allerdings weder um eine
run-time choice
Semantik, da alle möglichen
call-
Rewrite -Schritte
ausprobiert werden.
kinds
Es ist möglich
einzusetzen, um die Auswertungsstrategie zu ändern. Wir können
coin
zum Beispiel die Signatur von
kein
op
Error-Term
ändern, so dass nicht mehr zugesichert wird, dass
geliefert wird:
coin : −> [Nat]
.
double(coin) keine Rewrite-Regeln mehr
von double die Variable von der
Sorte Nat sein muss. Es muss also der Teilterm coin umgeschrieben werden, bevor die
Regel angewendet werden kann. Nach dieser Änderung liefert search nur noch {0, 2}
Das führt dazu das auf den gesamten Term
angewendet werden können, da in der
Rewrite-Regel
als Ergebnisse.
Um eine echte
Parameter für
dass keine
run-time choice
Semantik zu erzeugen, erlauben wir den
Error-Term
als
double. Auÿerdem setzten wir das Parameter auf frozen. Dies führt dazu,
Rewrite-Regel
zunächst die Regel für
auf diesen Teilterm angewendet werden darf. Daraus folgt, dass
double
angewendet werden muss.
10
4 Funktional-logische Programmierung in Full-Maude
double : [Nat] −> Nat [frozen (1)].
double(N:[Nat]) => N:[Nat] + N:[Nat]
op
rl
Wir erhalten nun wieder
{0, 1, 2}
.
als Ergebnismenge.
Ein typisches Beispiel für die Anwendung von Nicht-Determinismus ist die Permutation
von Listen. Dabei wird eine Funktion
insert
Funktion aufruft und nicht-deterministisch Permutationen der Liste zurück lie-
fert. Dies kann in
mod
permute deniert, welche eine nicht-deterministische
Maude
PERMUTATION is
LIST{Nat}
protecting
X,Y : Nat .
XS : List{Nat}
umgesetzt werden:
.
vars
var
.
permute : List{Nat} −> [List{Nat}] .
permute(nil) => nil .
permute(X XS) => insert(X, permute(XS))
op
rl
rl
insert : Nat List{Nat} −> [List{Nat}]
insert (X, nil) => X nil .
insert (X, Y XS) => X Y XS .
insert (X, Y XS) => Y insert(X, XS) .
op
rl
rl
rl
.
.
endm
In diesem Fall wird wieder
call-time choice
benutzt. Nun lassen sich zum Beispiel nicht-
deterministisch alle Permutationen der Liste
Maude> search in PERMUTATION : permute(1
N:List{Nat} −−> 1 3
N:List{Nat} −−> 3 1
No more solutions.
[1 2 3]
2 3) =>! 2
berechnen, die mit
N:List{Nat}
2
anfangen:
.
4.2 Gleichheitsconstraint
In funktional-logischen Programmiersprachen kann die Anwendung von Gleichungen an
die Erfüllung von Bedingungen geknüpft werden. In Curry gibt es dafür den Operator
=:=.
Dieser Operator kann als ein
Constraint
verstanden werden, welches erfüllt wer-
den muss, damit die Gleichung angewendet werden kann. Dieser
e1 =:=e2
wird genau dann erfüllt, wenn eine Belegung für
kann, so dass
In
Curry
e1 ==e2
kann weiterhin der Operator
auch als
Expression
und
e2
gefunden werden
gilt.
&>
verwendet werden, um einen
einen Ausdruck zu binden. Dabei wird ein Term
e
e1
Gleichheitsconstraint
Constraint Expression
Constraint
bezeichnet. Ein Beispiel für eine solche
ist die eine Denition der Subtraktion auf den Peano-Zahlen:
11
an
c &> e mit Constraint c und Ausdruck
Constraint
4 Funktional-logische Programmierung in Full-Maude
data
Peano = Z e r o
Peano
−>
|
P Peano
add
::
add
Zero
add
(P n ) m = P ( add
sub
n m = add
where
Peano
−>
Peano
n = n
x
n m)
x m =:= n &> x
free
x
In diesem Beispiel wird eine freie Variable
Gleichheitsconstraint
Der
ner
eingeführt, mit einer Suche auf ihr der
gelöst und diese als Ergebnis der Subtraktion ausgegeben.
Gleichheitsconstraint kann in Maude
Rewrite-Regel deniert werden:
Success .
success : −> Success [ctor] .
op _=:=_ : Nat Nat −> [Success] [comm]
rl X:Nat =:= X:Nat => success .
mit Hilfe einer Konstanten
success
und ei-
sort
op
.
Es wird in diesem Fall angenommen, dass als Resultat von nicht-deterministischen Operatoren immer als
call-time choice
kind
deklariert wird. Wie oben gezeigt wurde, führt dies zu einer
Semantik, wenn die Funktionsargument als Sorten deklariert werden.
Dies ist für den Operator
=:=
der Fall. Ansonsten würde
coin =:= coin
zu
success
ausgewertet werden, ohne das der Nicht-Determinismus aufgelöst würde.
Um eine
Constraint Expression
zu ermöglichen muss noch ein weiterer Operator
niert werden. Diesen nennen wir in
Maude
_>>_ : [Success] [Nat] −> [Nat] [frozen
success >> X:[Nat] => X:[Nat] .
op
rl
In der Deklaration des Operators
in
(2)
strat
de-
(1 0)] .
ist das zweite Argument als
dazu führt, dass dieses Argument nicht durch
&>
um und denieren ihn folgendermaÿen:
Rewriting
frozen
markiert, was
ersetzt werden kann. Dies hat
zur Konsequenz, dass das zweite Argument niemals durch die Anwendung einer Regel
verändert wird, bevor das erste Argument zu
Semantik des
Constraint
success
umgeschrieben wurde, was der
entspricht. Der Operator ist weiterhin mit
strat 1 0
gekenn-
zeichnet, was bedeutet, dass zunächst das erste Argument vereinfacht wird, bevor der
gesamte Term vereinfacht werden kann. Dies ist wichtig, da auf diese Weise rekursive
Vereinfachungen abgefangen werden können.
Mit Hilfe des
riablen in
Gleichheitsconstraints
Maude
haben wir die Voraussetzung um ungebundene Va-
Programmen zu benutzen.
12
4 Funktional-logische Programmierung in Full-Maude
4.3 Freie Variablen
Ungebundene oder logische Variablen sind ein Merkmal von funktional-logischen Sprachen. Auch in
mit
Curry
ist es möglich, freie Variablen zu benutzen. Diese Variablen werden
where ... free
deklariert und es wird automatisch nach passenden Belegungen
gesucht. Im folgenden Beispiel sind
l a s t x s = y s ++[ e ] =:=
where y s , e f r e e
Die Funktion
ys
und
e
und
e
freie Variablen:
x s &> e
last berechnet das letzte Element einer Liste. Bei der Auswertung werden
mit passenden Werten belegt.
Das gleiche Verhalten kann in
Gleichheitsconstraint
mod
ys
CONSTRAINT
Full-Maude
erzeugt werden. Dazu wird zunächst das
auf den in 2.2 denierten Listen von natürlichen Zahlen
2 deniert:
is
DECOMPOSITION
protecting
.
Success .
success : −> Success [ctor] .
op _=:=_ : NatList NatList −> [Success] [comm]
rl X:NatList =:= X:NatList => success .
sort
op
.
_>>_ : [Success] [NatList] −> [NatList] [frozen
success >> X:[NatList] => X:[NatList] .
op
rl
(2)
strat
(1 0)] .
endm
Mit Hilfe dieses Operators können wir jetzt die Funktion
last : NatList −> [Nat] .
last(LS:NatList) => (R:NatList | L:Nat)
last
wie in
Curry
denieren:
op
rl
=:=
LS:NatList
>>
L:Nat [nonexec]
.
Mit Narrowing können wir damit das letzte Element einer Liste berechnen. In dieser
R
Denition kommen zwei freie Variablen
noexec-Attribut
verwenden, da
Maude
und
L
vor. Aus diesem Grund müssen wir das
dies sonst nicht erlaubt.
4.4 Residuation
In der logischen Programmierung gibt es verschiedene Strategien um das Lösen von
mehreren
Constraints
durch ein anderes
Residuation ist eine solche Strategie, deren AnConstraints zu pausieren, bis eine neue Instantiierung
zu beschleunigen.
satz darin besteht, das Lösen von
Constraint
erfolgt ist. Das Hauptprinzip hinter Residuation ist so lange
mit einem Funktionsaufruf in einem Constraint zu warten, bis die nicht-deterministische
Auswertung der Argumente beendet ist.
Residuation
steht unter anderem in
Curry
zur
Verfügung.
Residuation in Maude zu ermöglichen, wird zunächst ein Operator
tion von Constraints deniert:
Um
2
Allerdings ohne die Kommutativität der Konkatenation!
13
für die Kombina-
4 Funktional-logische Programmierung in Full-Maude
_& _
op
: [
Success] [Success] −> [Success] [assoc comm id: success]
.
Maude immer Vereinfachungen ausführt bevor Termersetzungsregeln angewendet werden, ist es relativ einfach Residuation umzusetzen, indem man die Funktionen als Gleichungen und die Instantiierung als Rewriting-Regeln deniert. Ein einfaches Programm,
Da
welches eine Variable nicht-deterministisch mit natürlichen Zahlen instantiiert und die
Addition als Gleichungen deniert, wurde von Escobar folgendermaÿen implementiert:
nat : Nat −> [Success]
nat(0) => success .
nat(s N) => nat(N) .
op
rl
rl
op
_+_ : Nat Nat −> [Nat]
M=M.
( s N ) + M = s ( N + M) .
.
.
eq 0 +
eq
Es kann nun eine Operation deniert werden, welche Residuation zur Berechnung benutzt:
sub : Nat −> Nat −> [Nat] .
sub(X:Nat, Y:Nat) => nat(Z:Nat)
op
rl
Es wird dabei immer zuerst
Z
&
Z:Nat
+
Y:Nat
=:=
X:Nat
>>
Z:Nat [nonexec]
.
instantiiert, bevor die Addition vereinfacht wird um den
Constraint zu lösen.
14
5 Zusammenfassung
In dieser Arbeit wurde beschrieben, wie funktional-logische Programmierung in
Maude
funktioniert. Dazu wurde die Programmier- und Spezikationssprache kurz beschrieben,
Maude eingegangen wurde. Es wurde dann die Erweiterung von Maude mit dem Namen Full-Maude
wobei auf Syntax und Semantik der funktionalen und Systemmodule von
und das in dieser enthaltene Modul für Narrowing vorgestellt.
Im Anschluss wurde gezeigt, wie Nicht-Determinismus,
Constraints
und freie Variablen
in Full-Maude so verwendet werden können, wie es in funktional-logischen Sprachen wie
Curry möglich ist.
Curry, wie Residuation, durch die Syntax und SeMaude -Programmen einfach umgesetzt werden können. Durch die Unterstüt-
Es wurde ersichtlich, dass Konzept aus
mantik von
zung von und Kombination aus gleichungsbasierten Vereinfachungen und regelbasierter,
nicht-deterministischer Termersetzung sind mächtige Werkzeuge für diese Anwendungsfälle gegeben.
Bisher wurde nur ein Narrowing Ansatz in
Full-Maude
umgesetzt.
Needed Narrowing,
eine Technologie die in Curry vorhanden ist und die der Bedarfsauswertung in funktionalen Programmen ähnelt, fehlt bislang in
Maude.
Wie performant die von Escobar implementierte Narrowing Lösung im Vergleich zu anderen Sprachen wie Curry ist, wurde in dieser Arbeit, wie auch in Escobars Aufsatz, nicht
evaluiert.
15
Literatur
Maude Manual (Version 2.7). März 2015.
[Cla+15]
Manuel Clavel u. a.
[Esc14]
Santiago Escobar. Functional Logic Programming in Maude. In:
tion, Algebra, and Software.
Specica-
Hrsg. von Shusaku Iida, José Meseguer und Ka-
zuhiro Ogata. Bd. 8373. Lecture Notes in Computer Science. Springer Berlin
Heidelberg, 2014, S. 315336.
[Han13]
M. Hanus. Functional Logic Programming: From Theory to Curry. In:
gramming Logics - Essays in Memory of Harald Ganzinger.
7797, 2013, S. 123168.
16
Pro-
Springer LNCS
Herunterladen