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
DeclarationsAndStatements>
is
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 [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