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