An der Uni Melbourne entstand eine Sprache, die unentbehrliche Programmierparadigmen vereint und bei Geschwindigkeit, Zuverlässigkeit und Skalierbarkeit überzeugt. Das Hightech -Produkt bewährt sich nicht nur in der Forschung, sondern auch in der Praxis.
Australien, bekannt für zahlreiche ungewöhnliche und spektakuläre Tierarten, bereichert die Zoologie um eine neuerliche Sensation: Die Eier legende Wollmilchsau, weniger von deutschen Tierforschern als durch Informatiker gebetsmühlenartig ins Reich der Legende verbannt, scheint auf dem Campus der Universität Melbourne [1] eine ökologische Nische zu bewohnen. Es handelt sich um die Programmiersprache Mercury, die funktional getypt ist wie Haskell, (O-)Caml und SML, logisch ist wie Prolog, aber ohne deren wesentliche Schwächen (Cut, I/O), also 100 Prozent deklarativ.
Das Wundertier unterstützt die Arbeit mit Higher Order Logic und zum größten Teil auch mit OO-Konstrukten (Design Patterns und so weiter) in Bälde vollständig. Nach den vorliegenden Benchmarks zu urteilen ist Mercury die mit Abstand schnellste Sprache im Bereich Logik und Constraints und spielt insgesamt in der Liga der Highspeed-Sprachen weit vorne mit. Sie ist für schnellste neuronale Netze (mit konventioneller Hardware) geeignet, besitzt vorbildliche Compilezeit-Fehlererkennung und Profiling und ist in ihrer Architektur für Großprojekte mit Hunderttausenden von Zeilen Code ausgelegt.
Im Mittelpunkt: Modes
Von Sprachen wie Corbas IDL oder Ada sind so genannte Modes bekannt, die zusätzlich zum gewohnten Typ die Lese- und die Schreibsemantik von Variablen festlegen, zum Beispiel mit den Schlüsselwörtern »in« und »out«. Mercury bezieht ihre Leistungsfähigkeit von einem ähnlichen, aber viel fundamentaleren System, das schließlich eine zu den Typen parallele Beschreibungsdimension bildet, an die man sich aber überraschend leicht gewöhnt.
Der Mercury-Grundansatz geht auf den französischen Logiker Jean-Yves Girard [2] zurück, der eine noch unterhalb der klassischen Logik angesiedelte, sehr Computing-freundliche Schicht entdeckte, die lineare Logik. Diesen Ansatz hat der von Haskell bekannte Philip Wadler [3] aufgegriffen, was eine wesentliche Anregung zur Entwicklung von Mercury war. Im Mercury-Team begleiten erstrangige Wissenschaftler die Entwicklung der Sprache, es sind zu viele, um sie hier aufzuzählen.
Fibonacci-Benchmark
Obwohl in vielen Distributionen bereits enthalten, empfehlen sich der Download der Sourcen bei [1] sowie »./configure ; make ; make install«. Das dauert auch auf schnellen Rechnern einigermaßen lange, dafür entstehen aber mehrere Compiler (so genannte Grades) für verschiedene Anwendungsfälle. Bei der Installation sollte niemand die Extras vergessen, die dann bereits Mercury selbst kompiliert:
mmake depend mmake mmake install
Der Linuxer genießt bei der Mercury-Programmierung Heimvorteil: Klicki-bunti-Oberflächen sucht der Benutzer hier vergebens, der Merkurianer bevorzugt Vi und (X-)Emacs (Abbildung 1). Als erstes Codebeispiel soll die Ermittlung der Fibonacci-Zahlen dienen.
Wie bei Java-Klassen müssen auch in Mercury Modul- und Dateinamen gleich lauten. Die Quelldatei »fibonacci.m« lässt sich mit folgenden Befehlen in ausführbaren Code übersetzen:
mmake fibonacci.depend mmake fibonacci
Die Wahl dieses Beispiels bestimmte ein Hintergedanke: Fibonacci-Zahlen gelten als einfacher Benchmark. Der Benutzer tippt einfach »time fibonacci 42« ein, liest die Sekundendifferenz ab und vergleicht mit anderen Sprachen. Auf dem Rechner des Autors waren das im Durchschnitt ziemlich genau 8 Sekunden, während Java (mit JDK 1.5) 7 und das entsprechende C-Programm (GCC 4.1) 10 Sekunden benötigten – ein vorsichtiger Wink, dass Mercury auch in herkömmlichen Anwendungsfeldern in puncto Geschwindigkeit vorne steht.
Für ernsthaftere Benchmarks sei auf [1] verwiesen. Werte im Vergleich mit Prolog-artigen Sprachen (schon etwas ältere Werte): 24- bis 116-mal schneller als SWI-Prolog und 3- bis 10-mal schneller als Sicstus Prolog, allerdings nur marginal schneller als Sicstus bei Queens. Für den Bereich Constraint-Solving sind unter [5] entsprechende Daten zu finden. Auch wenn alle Ergebnisse vom Mercury-Team selbst stammen, ist die dort ausgewiesene Performanz von Mercury wissenschaftlich plausibel.
Modulsystem
Die Eignung für Großprojekte mit Hunderttausenden von Codezeilen ist eines der Kernziele des Mercury-Teams, zumal ihr Compiler selbst in diese Kategorie fällt. Entsprechend hoch sind die Anforderungen an das Modulsystem. Für den Anwender macht sich dies darin bemerkbar, dass ein Modul (»:-module …«) die kleinste Code-Einheit darstellt. Es besteht aus einem öffentlichen (»:-interface …«) und in der Regel auch einem privaten (»:-implementation …«) Teil.
Fremde Module lassen sich unter anderem mit »:-import_module …« laden. Module können ineinander geschachtelt sein, und eine Datei darf außerdem auch mehrere Untermodule (vergleichbar zu inneren Klassen) enthalten, während ein Modul beim Import den Inhalt vieler Dateien vereinen kann. Dieses Modulsystem ist eng verbunden mit dem auf Gnu Make aufsetzenden Mmake. Spätestens wenn der Programmierer Bibliotheken mit Systemschnittstellen verwenden möchte, sollte er sich mit Mmake vertraut machen, da es dabei die Code-Erstellung organisiert.
Das Angebot an Mercury-Libraries ist durchaus eigenwillig zu nennen. Für eine Netzwerk-Verbindung basteln Programmierer auch schon mal mit TCP-Sockets. Das liegt nicht etwa an der mutwillig schlechten Unterstützung durch das Developer-Team, sondern eher an einem ausgeprägten Perfektionismus, der keinen Millimeter der reinen Kernkonzeption abzutreten bereit ist – neue Features gelangen nur nach Bestehen sehr anspruchsvoller Reviews in den Compiler. Daher verwundert es auch nicht mehr, wenn viele für andere Sprachen zentrale Bibliotheken in das Extras-Paket ausgelagert sind. Dies enthält dafür aber alles, was in der Regel für technisch anspruchsvolle Pakete nötig ist.
Modes und Prädikate
Erklärungsbedürftig ist für viele Programmierer imperativer Sprachen der Begriff des Prädikats. Während eine Funktion oder Methode als eine genau vereinbarte Einrichtung zur Umwandlung von Eingabe- in Ausgabevariablen vorstellbar ist, sind Prädikate beim Umgang mit unvollständigen Daten toleranter: Es muss nicht von vornherein geklärt sein, welche Variable welche andere Variable bestimmt. Der Programmierer trägt vielmehr eine lockere Menge von Bedingungen zusammen und lässt das System anschließend ermitteln, wie alles zusammenpasst. Ähnliches ist von SQL-Queries bekannt.
Die Hoffnung, auf diese Weise ganze Programme zu schreiben, also das Wie der Ausführung völlig der Maschine zu überlassen, war Triebfeder der Prolog-Euphorie Ende des letzten Jahrhunderts, in der insbesondere die Japaner das Roboterzeitalter einläuten wollten. Tatsächlich sind diesem Ansatz aber von Seiten der Logik gewisse Grenzen gesetzt, was zu dem Problem führt, dass Prolog von seiner Konzeption her mehr verspricht, als es halten kann. Die Abkehr von dieser Strategie ist ein wesentliches Kennzeichen von Mercury.
Eine der wichtigsten Besonderheiten von Mercury ist die durchgängige Verwendung von Modes. Bei anderen Sprachen wie IDL oder Ada gibt es sie zwar ebenfalls, bei Mercury nehmen sie aber syntaktisch eine ganz andere Dimension ein. Für den Anfang genügt es, vier Modes zu kennen: »in« und »out« sind beinahe selbsterklärend, sie stehen für Daten, die in einen Codeblock einfließen beziehungsweise diesen verlassen. Mercury trennt hierbei scharf, ein »inout« gibt es also nicht.
Das Wesen von Mercury offenbart sich auch noch in zwei weiteren Modes, die im obigen Fibonacci-Beispiel in der Mode-Deklaration von »main/2« (Listing 1, Zeile 6) erscheinen: »di« (Destructive Input) bedeutet, dass beim Eingang in den Codeblock alle anderen Referenzen auf die Dateneinheit zu zerstören sind, »uo« (Unique Output) bedeutet, dass die Dateneinheit von dem Codeblock nur einmal weitergegeben werden darf, beides zusammen sichert also die Einmaligkeit des Auftretens bestimmter Dateneinheiten, die durch ein Programm hindurchgefädelt werden.
Listing 1: »fibonacci.m« |
01 :-module fibonacci.
02 :-interface.
03 :-import_module io.
04
05 :-pred main(io, io).
06 :-mode main(di, uo) is det.
07
08 :-func fibonacci(int)= int.
09
10 :-implementation.
11 :-import_module int, list, string.
12
13 main(!IO) :-
14 command_line_arguments(Args, !IO),
15 (if Args = [Arg|_] then
16 Zahl = det_to_int(Arg),
17 format("Fibonacci-Zahl von %d ist %dn",
18 [ i(Zahl), i(fibonacci(Zahl)) ],
19 !IO)
20 else
21 true
22 ).
23
24 fibonacci(Zahl)=
25 (if Zahl < 2 then
26 1
27 else
28 fibonacci(Zahl - 1) + fibonacci(Zahl - 2)
29 ).
|
I/O deklarativ
Der wohl auffälligste Anwendungsfall solcher Unique-Modes ist die Ein- und Ausgabe, die sich dadurch endlich – anders als in Prolog-ähnlichen Programmen – deklarativ erledigen lässt. Das Fädeln versteckt sich hinter dem Ausdruck »!IO«. Diese Kurzform ist nur syntaktischer Zucker für folgende Form:
main(IO_0, IO_2) :-
write_string("Erstens:n", IO_0, IO_1),
write_string("Zweitens:n", IO_1, IO_2).
Die Verwendung von »!IO« erspart dem Programmierer das Durchnummerieren der Variablen. Allgemein erlauben diese Unique-Modes die Verwendung wesentlicher prozeduraler Sprachkonstrukte, ohne die Deklarativität opfern zu müssen – ein wichtiger Grund für die Leistungsfähigkeit der Sprache.
Eine weitere Eigenschaft der Mode-Vereinbarung für Signaturen ist, dass aus ihnen der Determinismus genauer abzuleiten ist. Die wichtigsten Arten sind »det« (genau eine gültige Belegung), »multi« (mindestens eine gültige Belegung, mehrere möglich), »semidet« (kann scheitern, aber höchstens eine gültige Belegung) und »nondet« (unbestimmt). Damit klärt sich die Beziehung zwischen den Deklarationen von Prädikaten (»:-pred …«) und Funktionen (»:-func …«): Letztere sind nichts anderes als ein Sonderfall mit genau einem »out«-Argument und sonst nur »in«-Argumenten, deren Determinismus »det« ist (Funktionen ohne »in«-Argumente sind Konstanten). Aufgrund dieser Vorgabe lassen sich Funktionen vereinbaren, ohne den Mode anzugeben.
Typsystem
Das Typsystem lehnt sich stark an jenes getypter funktionaler Sprachen an. Es genügt daher, kurz den Discriminated-Union-Typkonstruktor vorzustellen, der C-Programmierer an eine Mischung aus Enumeration, Union und Struct erinnern dürfte. Es ist eine Sammlung durch Semikolon getrennter unterschiedlicher Identifikatoren, die ihrerseits als Parameter weitere Felder enthalten können:
:-type adresse --->
adresse(strasse :: string,
plz :: int,
ort :: string);
immer_unterwegs ;
unbekannt .
Die Namen erlauben einen Zugriff ähnlich den Gettern und Settern in objektorientierten Sprachen. Natürlich kennt das Mercury-Typsystem Generics (in C++ heißen sie Templates), tatsächlich hat selbst Java sie bei getypten funktionalen Sprachen abgeschaut. Hier ein Typkonstruktor für Listen eines selbst bestimmbaren Element-Typs:
:-type list(Typ) --->
[] ; %% = nil
[ Typ | list(Typ) ] .%% = cons
Das folgende, etwas umfangreichere Beispiel enthält ein Prädikat »append/3« auf Listen, bei dem das dritte Argument einer Verkettung der ersten beiden Argumente entsprechen soll. In Prolog versprechen Programmierer oft mehr, als sie halten können, indem sie hierfür lediglich vereinbaren:
append([], Liste, Liste).
append([Erstes|LRest], Rechz, [Erstes|GesamtRest]) :-
append_1(LRest, Rechz, GesamtRest).
Unhaltbar ist hier zum einen, dass dieses Konstrukt unsinnige, gar nicht beabsichtigte Modes wie »append(out, out, out)« indirekt zusagt. Zum anderen zeigt sich, dass die gegebene Vereinbarung nicht für alle Modes effizient sein kann: Für »append(out, in, in)« – wenn die linke Teilliste zu ermitteln ist – taugt sie wenig. In Mercury lässt sich die Aufgabe korrekt erledigen, indem der Programmierer für solche Sonderfälle andere Prädikate verwendet (Listing 2).
Listing 2: »append/3« |
01 :-interface. 02 03 :-pred append(list(T), list(T), list(T)). 04 :-mode append(in, in, in) is semidet. 05 :-mode append(in, in, out) is det. 06 :-mode append(in, out, in) is semidet. 07 :-mode append(out, in, in) is semidet. 08 :-mode append(out, out, in) is multi. 09 10 :-implementation. 11 12 :-pragma promise_pure(append/3). 13 append(Linx::in, Rechz::in, Gesamt::out) :- 14 append_1(Linx, Rechz, Gesamt). 15 append(Linx::in, Rechz::out, Gesamt::in) :- 16 append_1(Linx, Rechz, Gesamt). 17 append(Linx::out, Rechz::out, Gesamt::in) :- 18 append_1(Linx, Rechz, Gesamt). 19 append(Linx::out, Rechz::in, Gesamt::in) :- 20 append_2(Linx, Rechz, Gesamt). 21 22 :-pred append_1(list(T), list(T), list(T)). 23 :-mode append_1(in, in, in) is semidet. 24 :-mode append_1(in, in, out) is det. 25 :-mode append_1(in, out, in) is semidet. 26 :-mode append_1(out, out, in) is multi. 27 append_1([], Liste, Liste). 28 append_1([Erstes|LRest], Rechz, [Erstes|GesamtRest]) :- 29 append_1(LRest, Rechz, GesamtRest). 30 31 :-pred append_2(list(T), list(T), list(T)). 32 :-pred append_2(out, in, in) is semidet. 33 append_2(Linx, Rechz, Gesamt) :- 34 ... |
Die vielen Mode-Deklarationen bedeuten natürlich viel Schreibarbeit, aber in den seltensten Fällen sind auch alle Mode-Kombinationen vorgesehen. In der Regel gibt es also Einschränkungen, die sich günstig auf Zuverlässigkeit und Ausführgeschwindigkeit auswirken.
Das Handling einer unbestimmbaren Anzahl von Lösungen erledigt Mercury denkbar elegant: Von einem Prädikat, das nicht »det« ist, ergibt sich eine Lösungsmenge, zum Beispiel in Gestalt einer (duplikatfreien) Liste mit Hilfe einer Funktion »solutions«. Mercury erlaubt nämlich Higher Order Logic, was bedeutet, dass Funktionen oder Prädikate selbst als Argumente in Funktionen oder Prädikaten auftreten dürfen.
Bei dieser Gelegenheit ist noch zu erwähnen, dass die funktionale Ausstattung von Mercury keinesfalls ein halbherziges Gimmick, sondern durchaus der von Sprachen wie Haskell, Caml und SML ebenbürtig ist. Der Autor hat sich die Übungsaufgaben einschlägiger Lehrbücher systematisch vorgenommen und konnte sie – mit leicht abgewandelter Syntax – genauso einfach lösen wie mit den vorgesehenen Sprachen.
Tk-GUI
Das erste praktische Beispiel stammt aus dem Bereich der GUI-Programmierung: Einer Taste ist eine einfache Anweisung zuzuordnen, die im Fall eines Klicks abläuft. Wie viele andere Sprachen besitzt Mercury ein Tcl/Tk-Interface, das nicht zuletzt wegen seiner leichten Handhabbarkeit sehr beliebt ist.
Die Liste der über die Mercury-Schnittstelle verfügbaren Tk-GUI-Elemente ist nicht vollständig, aber leicht mit einigen Grundkenntnissen in C und Tcl/Tk erweiterbar: Nativer C-Code lässt sich in Mercury einbetten, indem man ihn in den Mercury-Quelltext schreibt, der Rest geschieht automatisch. Das ist fraglos sehr elegant und wird ergänzt durch die Harmonie mit der ebenfalls sehr C-freundlichen Tcl/Tk-Schnittstelle. Außerdem teilen Mercury und Tcl/Tk noch eine String-Schnittstelle. So gerät etwa eine Erweiterung um eine »cget«-Auslesung zum Vierzeiler (Listing 3).
Listing 3: »cget« |
01 cget_string(TclVersteher, Widget, ConfName, Ergebnis, !IO) :- 02 unwrap(Widget, WidgetId), 03 eval(TclVersteher, WidgetId++" cget -"++ConfName, Erfolg, Ergebnis, !IO), 04 (if Erfolg = tcl_ok then true else error(Ergebnis) ). |
Der Autor setzte die Tk-Beispielaufgaben eines bekannten Lehrbuches systematisch in Mercury um. Nötige Erweiterungen waren in der Regel eine Frage von Minuten. Die GUI-Programmierung ist aber auch ein gutes Anwendungsbeispiel für die fortgeschrittenen Eigenschaften der Sprache (Listing 4).
Listing 4: GUI-Tasten mit auszulösender Aktion |
01 main(!IO) :-
02 mtcltk.main(
03 pred(TkVersteher::in, I::di, O::uo) is det :-
04 aufgabe(TkVersteher, I, O),
05 ["Tasten"],
06 !IO).
07
08 :-pred aufgabe(tcl_interp, io, io).
09 :-mode aufgabe(in, di, uo) is det.
10 aufgabe(TkVersteher, !IO) :-
11 GesamtCockpit = mtk_core.root_window,
12 configure(TkVersteher,
13 GesamtCockpit,
14 [height(40), width(400), background("green"), padx(50), pady(10)],
15 !IO),
16 bildeTaste(":-0", "yellow", TkVersteher, GesamtCockpit, GelbeTaste, !IO),
17 bildeTaste(":-o", "red", TkVersteher, GesamtCockpit, RoteTaste, !IO),
18 bildeTaste(";-)", "blue", TkVersteher, GesamtCockpit, BlaueTaste, !IO),
19 mtk.pack(TkVersteher,
20 [pack(GelbeTaste, []), pack(RoteTaste, []), pack(BlaueTaste, [])],
21 !IO).
22
23 :-pred bildeTaste(string, string, tcl_interp, widget, widget, io, io).
24 :-mode bildeTaste(in, in, in, in(toplevel), out, di, uo) is det.
25 bildeTaste(Beschriftung, Farbe, TkVersteher, GesamtCockpit, Taste, !IO) :-
26 mtk.button(TkVersteher,
27 [text(Beschriftung), background(Farbe), active_background(Farbe), padx(50)],
28 GesamtCockpit,
29 Taste,
30 !IO),
31 configure(TkVersteher, Taste, [command(meldeFarbe(Taste))], !IO).
32
33 :-pred meldeFarbe(widget, tcl_interp, io, io).
34 :-mode meldeFarbe(in(button), in, di, uo) is det.
35 meldeFarbe(Taste, TkVersteher, !IO) :-
36 cget_string(TkVersteher, Taste, "background", HgFarbe, !IO),
37 io.write_string("klicked: '-background "++HgFarbe++"'", !IO), nl(!IO).
|
Prädikate als Argumente
Das Beispiel verwendet Namespaces wie »mtcltk.main«, von denen bisher nicht die Rede war. Gleich zweimal wird hier ein Prädikat als Argument übergeben: Einmal erhält »meldeFarbe/4« so die auszulösende Aktion der Taste, zum anderen verarbeitet der Tcl/Tk-Interpreter die Art und Weise der Ausgabe in Gestalt des Prädikats »aufgabe/3«. In der Mercury-Praxis zeigt sich, dass Higher-Order-Ausdrücke keine Kuriosität sind, sondern im Gegenteil sehr rasch zur guten Gewohnheit werden, die das Leben bequemer macht – kein Vergleich etwa mit der umständlichen Handhabung solcher Fälle in Java.
Einer der besonderen Vorzüge der Tk-Syntax ist, dass sich die Widgets durch die bequeme Aneinanderreihung von Parametern konfigurieren lassen. In einer Zeile lässt sich oft so viel ausdrücken, dass anderswo zehn dafür nicht reichen. Es ist erfreulich, dass Mercury diesen Ansatz übernommen hat – die Parameter finden sich einfach in Listen wieder.
Insts und Modes
Die Modes »in«, »out«, »di« und »uo« stellen nur die Spitze des Eisbergs dar. Zu Ende gedacht bedeutet ein Mode-System eine zusätzliche Beschreibungsdimension komplexer zusammengesetzter Modes, deren Vielfalt hinter jener des Typsystems kein bisschen zurücksteht. Ein Mode ist im Wesentlichen weiter zerlegbar in zwei Instanziierungs-Zustände, ja er stellt sogar einen Übergang von einem zum anderen dar.
Die einfachsten Zustände sind »bound« (durch den Zusammenhang bereits irgendwie bestimmt) und »free« (auf keine Weise durch den Zusammenhang bestimmt). So entspricht »in« einem Übergang »bound >> bound« und »out« einem Übergang »free >> bound«.
Die Komplexität der Mercury-Modes lässt sich bis zu den Instanziierungs-Zuständen zurückverfolgen, die sich auch mit einer eigenen Deklaration »:-inst …« vereinbaren lassen. In Listing 4 haben GUI-Elemente eigene Instanziierungs-Zustände, aus denen durch Klammerung entsprechende Modes wie »in(toplevel)« oder »in(button)« entstehen. Dazu kommen noch die Modes von Funktionen und Prädikaten, deren munterer Verkehr ja ein wesentliches Kennzeichen der Sprache ist. Beispielsweise ist für »:-pred mtcltk.main(pred(tcl_interp, io, io), list(string), io, io).« der Mode »:-pred mtcltk.main(in(pred(in, di, uo) is det), in, di, uo) is det«. Das »pred(in, di, uo) is det« entspricht hier einem »inst«, und »in(pred(in, di, uo) is det)« entspricht dem Übergang »(pred(in, di, uo) is det) >> (pred(in, di, uo) is det)«.
Ebenso haben die Parameter der Tk-Widget-Konfigurationsliste individuell verschiedene Instanziierungs-Zustände (Listing 5). Wer sich daran gewöhnt hat, das zu bedenken, dem geht das Ganze relativ unbeschwert wie bei der Typ-/Klassen-Vereinbarung von der Hand.
Listing 5: Tk-Instanziierung |
01 :- inst button_config 02 ---> ... 03 ; background(ground) 04 ; ... 05 ; command(pred(in, di, uo) is det) 06 ; ... 07 ; text(ground) 08 ; ... |
Objektorientiert
Das Programmieren in Mercury ist in sehr vielen Stilen möglich, sodass Einsteiger mit einem ihnen vertrauten beginnen können. Weil die Paradigmen fließend ineinander übergehen, führen viele Wege zum Ziel. Da insbesondere die Objektorientierung sich großer Verbreitung erfreut, stellt sich noch die Frage, inwieweit Mercury OO-Programmierern eine Übernahme ihrer bewährten Verfahrensweisen erlaubt.
Es soll nicht verheimlicht werden, dass einige feinere Details der OO-Umsetzung noch fehlen – nach Informationen der Entwickler sollen sie im Laufe der nächsten Wochen folgen. Zum überwiegenden Teil ist der Einsatz gängiger objektorientierter Verfahren bereits jetzt möglich: Mercury verfügt zum einen über eine Corba-Schnittstelle, zum anderen ist das Typsystem so beschaffen, dass sich bestehende OO-Konstrukte aus Sprachen wie Java und C++ sehr leicht in Mercury abbilden lassen. OO-Programmierer können also vertraute Architektur-Patterns recht einfach übernehmen.
Typklassen
Gewöhnungsbedürftig ist anfangs jedoch die Notation, die darauf verzichtet, das Objekt beim Funktions- oder Prädikatsaufruf hinzuschreiben. Hier wäre ein wenig syntaktischer Zucker für Programmierer mit OO-Herkunft wünschenswert. Die Typklassen »:-typeclass …« entsprechen weitgehend Java-Interfaces. Ein Lowlevel-Stream sollte zum Beispiel das Auslesen des Fehlerstatus zulassen (Listing 6).
Listing 6: Typklasse |
01 :-typeclass lowlevel(STREAM_TYP) where [ 02 % Stream, Meldung, ObFehler, !IO 03 pred get_error(STREAM_TYP, string, bool, io, io), 04 pred get_error(in, out, out, di, uo) is det 05 ]. |
Die Implementation orientiert sich an konkreten Typen, die eigentlich das Äquivalent einer (methodenlosen) Klasse in Mercury darstellen. Ist ein geeigneter Typ gefunden, ergänzt der Programmierer in einer »:-instance …«-Deklaration die Methoden gemäß dem jeweiligen Typklassen-Interface (Listing 7). Die Bezugnahme auf ein Interface ist allerdings etwas umständlicher als sonst (Listing 8). Entsprechend lassen sich die Typklassen erweitern, auch Mehrfachvererbung ist möglich (Listing 9). Auf ähnliche Weise sind mit der »instance«-Deklaration Interfaces klassenbezogen einsetzbar (Listing 10).
Listing 7: Typklasse und Methoden |
01 :-type streamTyp ---> ... 02 :-instance lowlevel(streamTyp) where [ 03 (get_error(Stream, Meldung, ObFehler, !IO) :- 04 ... 05 ) 06 ]. |
Listing 8: Interface |
01 :-pred nutzeStream(S, io, io) <= lowlevel(S).
02 :-mode nutzeStream(in, di, uo).
03 nutzeStream(Stream, !IO) :-
04 ...
05 get_error(S, Meldung, GibtFehler, !IO),
06 (if GibtFehler = yes then
07 write_string("Fehler: "++Meldung++"n", !IO)
08 else ...
|
Listing 9: Typklasse erweitern |
01 :-typeclass output(STREAM_TYP) <= lowlevel(STREAM_TYP) where [ 02 % Stream, Char, ObErfolg, !IO 03 pred write_char(STREAM_TYP, char, bool, io, io), 04 pred write_char(in, in, out, di, uo) is det 05 ]. |
Listing 10: Klassenbezogenes Interface |
01 :-typeclass ostreamSammlung(OSTREAMS) where [ 02 pred schreibCharInAlle(OSTREAMS, char, io, io), 03 mode schreibCharInAlle(in, in, di, uo) is det 04 ]. 05 :-instance ostreamSammlung(list(OSTREAM)) <= output(OSTREAM) where [ 06 schreibCharInAlle([], _Char, !IO), 07 (schreibCharInAlle([OStream|Rest], Char, !IO) :- 08 write_char(OStream, Char, _, !IO), 09 schreibCharInAlle(Rest, Char, !IO) 10 ) 11 ]. |
Zwar erhält das Typklassen-System wie erwähnt erst in diesen Wochen den letzten Schliff. Aus der objektorientierten Welt vertraute architektonische Gebilde sind aber schon jetzt ohne Klimmzüge in Mercury ebenfalls möglich. Einschränkungen auf diesem Gebiet sind mittlerweile die Ausnahme.
Constraint Solving
Constraint Solving bedeutet, dass ein Programmiersystem eine Reihe einzuhaltender Bedingungen erhält und es dann selbstständig passende Lösungen ermittelt – entsprechend der Idee, dass der Nutzer nur noch die Aufgabe formuliert. Constraint Solving hat tatsächlich in der Praxis vielfältige Planungsprobleme lösen können, gerade auch im Hochtechnologie-Bereich.
Obwohl die akademischen Lehre Constraint Solving gern als Musterbeispiel für den Einsatz von Prolog vorführt, hat sich die Effizienz eines Prolog-gestützten Constraint Solving in der Praxis oft als unbefriedigend gezeigt. Viele sind daher dazu übergegangen, Constraint-Solving-Systeme in herkömmliche, nicht deklarative Sprachen zu implementieren, um eine bessere Performanz zu erzielen.
Hilfe von HAL
Mercury unterstützte lange Zeit kein Constraint Solving, da es nicht so leicht mit den für die Sprache geltenden Anforderungen vereinbar ist [4]. Andererseits stützte sich das HAL-Projekt [5], das erhebliche Performanz-Fortschritte verzeichnen konnte, auf Mercury. Seit einiger Zeit ist das HAL-Projekt beendet und sein Code fließt wieder in Mercury ein. Dieser Prozess ist zu erheblichen Teilen abgeschlossen, sodass Mercury mittlerweile als eines der leistungsfähigsten Constraint-Solversysteme gilt.
Auch die Einführung von Constraint Solving schlägt sich in einer Erweiterung des Mode-Systems nieder. Die Instanziierungs-Zustände »free« und »bound« werden durch »any« ergänzt, was so viel wie “noch nicht spezifiziert” bedeutet – bei Constraint-Aufgaben ergibt sich nämlich erst aus der Fragestellung selbst, welche Dateneinheiten wodurch bedingt sind. Ein einigermaßen komplexes Solver-Typsystem ergänzt dies so weit, dass es die für Constraint Solving kennzeichnenden Mode-Unklarheiten abfängt.
Die Compiler-Distribution enthält bereits alles Wichtige, was zum Bau eines Compilers erforderlich ist: Aggregat-Typen wie Bäume und Mengen, Lexer, Parser, Syntaxverarbeitung, Zufallszahlen, Benchmarking, Fehlerbehandlung und Ähnliches. Andere Anwendungsbereiche sucht man in der Regel besser in der Extras-Distribution. Dort finden sich zum Beispiel CGI-Support, die ODBC-Schnittstelle, Stream-Handling und Sockets. Ebenfalls praxisrelevant ist die dort vertretene Verarbeitung von XML. Wer auf Microsoft-Plattformen arbeitet, kann über den Windows-Installer-Generator direkt MSI-Dateien erzeugen.
GUIs für Mercury mit Tk oder OpenGL
Für Benutzeroberflächen stehen mehrere Wege offen: So gibt es zwei Curses-Bibliotheken, um nach alter Schule auf der Konsole zu arbeiten. Als GUI-Baukasten gibt es einerseits die schlanke, Xlib-basierte Easy-X-Library, andererseits das bewährte und bereits vorgestellte Tcl/Tk. Wer höchste Ansprüche hegt, dem steht für grafische Oberflächen noch die OpenGL-Schnittstelle zur Verfügung (Abbildung 3).

Abbildung 3: Für anspruchsvolle Benutzer-Schnittstellen und 3D-Präsentation verfügt Mercury über eine OpenGL-Bibliothek.
Constraint Solving bedienen die Pakete »clpr« (für Fließkommazahl-Domänen) und »solver_types«. Das Mercury-Gegenstücke zu Flex und Bison findet sich in »lex« und »moose«. Für wissenschaftliche Berechnungen gibt es Bibliotheken mit komplexen Zahlen oder genetischen Algorithmen. Jüngst haben die Entwickler noch einen Baukasten für neuronale Netze dazugepackt, der zu den schnellsten seiner Art gehört.
Erfahrung oder Abenteuerlust
Alles in allem erfordert Mercury ein gesundes Maß an Pioniergeist und Hackerkultur, um über diverse kleinere Unebenheiten locker hinweggehen zu können. Erfahrene Linuxer, die sich mal auf ein richtiges, halbwildes Rassepferd setzen wollen, können erfolgreich auch im kommerziellen Rahmen tätig werden. Aber auch Einsteiger mit weniger Geduld, die nur Beispiele ausprobieren möchten, haben einen spannenden Nachmittag und kriegen einen Einblick, was mit Mercury möglich ist. (ofr)
Infos |
|
[1] Projekt-Website: [http://www.mercury.csse.unimelb.edu.au] [2] Jean-Yves Girard, Directeur de Recherche am CNRS: [http://iml.univ-mrs.fr/~girard] [3] Philip Wadler, Uni Edinburgh: [http://homepages.inf.ed.ac.uk/wadler] [4] Ralph Becket, Maria Garcia de la Banda, Kim Marriott, Zoltan Somogyi, Peter J. Stuckey & Mark Wallace, “Adding constraint solving to Mercury”: Proceedings of the Eighth International Symposium on Practical Aspects of Declarative languages, Charleston, South Carolina, January 2006, Seite 16 ff. Das Paper ist auch unter [1] zu finden. [5] HAL-Projekt: [http://www.csse.monash.edu.au/~mbanda/hal/index.html] |
Der Autor |
|
Nick Rudnick arbeitet akademisch in den Bereichen Wissensmanagement, KI und Simulation. Als selbstständiger Entwickler stellt er gerade ein kommerzielles Server-Client-System auf Mercury-Basis für den Einsatz im Konsumgüterbereich fertig. Er glaubt außerdem, dass Frauen die wahren Programmierer sind, und harrt der Zeit, da sich dies offenbaren wird. |







