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