Runde Sache

Pugs ist ein vor zwei Jahren begonnenes Projekt zur Implementierung von Perl 6. Mit seiner Hilfe kann man schon jetzt in der nächsten Version der Skriptsprache programmieren.

vorlesen Druckansicht 6 Kommentare lesen
Lesezeit: 12 Min.
Von
  • Steffen Schwigon
Inhaltsverzeichnis

Ursprünglich war Pugs (Perl 6 User’s Golfing System), eine Perl 6-Implementierung in Haskell, ins Leben gerufen und maßgeblich vorangetrieben von Audrey Tang. Es besteht primär aus dem in Haskell geschriebenen Compiler. Das Repository hat sich inzwischen zum Rahmenprojekt für sämtliche Dinge rund um Perl 6 entwickelt. Es beherbergt mehrere Compiler-Projekte, eine Suite mit Beispielen, Modulen, circa 20 000 in Perl 6 geschriebenen Tests und Dokumentation.

Backends übersetzen Perl 6 in verschiedene Sprachen (unter anderem Haskell, Parrot, Perl 5, Javascript). Von ihnen ist das für Haskell am weitesten fortgeschritten. Messlatte für den Erfolg ist das Absolvieren der Testsuite. Die verschiedenen Compiler-Projekte widmen sich für den Anfang unterschiedlichen Teilmengen von Perl 6. Diese Implementierungen können zum Beispiel als Bootstrapper für Perl 6 dienen - Ziel ist es, den Interpreter in der Sprache selbst zu schreiben.

Sich asymptotisch 2π nähernde Versionsnummern identifizieren die Milestones des Pugs-Projekts. Zur aktuellen Wegmarke 6.28 gehört das OO-System. Dessen Semantik entsteht gleichzeitig in Form der Perl 5-Bibliothek „Moose“. Sie illustriert, wie die Arbeit an Perl 6 auf die Entwicklung des Vorgängers zurückwirkt.

In 6.283 sollen Regeln (Rules) und Grammatiken (Grammars), die Weiterführung der regulären Ausdrücke, vollständig funktionieren. Reguläre Ausdrücke in Perl 6-Syntax gibt es schon seit Beginn des Projekts, und auch Rules, die Grundbausteine einer Grammatik, sind bereits zu einem brauchbaren Teil implementiert. Version 6.2831 soll das Typsystem enthalten, und für 6.28318 sind Makros angekündigt.

Mehr Infos

Larry Wall hat die Syntax der Sprache fast vollständig in einer Perl 6-Grammar definiert. Die Syntax und Semantik der Sprache stabilisieren sich zunehmend, seit man mit Pugs besser in Randbereichen experimentieren und Erfahrungen sammeln kann.

Einen Einstieg zu vielen weiteren Informationen rund um das Projekt bietet pugscode.org. Hier liegen Vorträge von Konferenzen, es gibt eine Online-Shell zum Ausprobieren, ein Wiki, Erläuterungen zum Herunterladen via SVN, die Statistiken der Test-Durchläufe auf verschiedenen Systemen und insbesondere die verbindliche Perl 6-Spezifikation, die sogenannten Synopsen.

Eine Synopsis ist der aktuelle Dokumentationstyp nach RFC, Apocalypse und Exegese. Zur Erinnerung: Larry Wall fing an, die von Anwendern geschriebenen RFCs und seine Gedanken in den Apocalypse-Dokumenten zu sortieren, die entsprechend den Themen der Kapitel im Perl-Kamel-Buch nummeriert waren. Diese Dokumente sind inzwischen veraltet, geben allerdings noch immer Einblick in Walls Denken und die ursprünglichen Ideen hinter dem Sprachdesign. In den Exegesen erläuterte Damian Conway die Apocalypse ausführlicher, und die Synopsen waren als die endgültige Dokumentation der Implementierung gedacht. In ihnen konzentrieren sich derzeit die Spezifikation und Beschreibung der Sprache. Neue Kapitel entstehen nur noch als Synopsen; ihre Nummerierung (etwa „S12“) entspricht dem ursprünglichen Schema.

Da Pugs ein testgetriebenes Projekt ist, haben sich die Entwickler eine nützliche Verschmelzung der Synopsen mit den zugehörigen Code-Snippets aus der Test-Suite ausgedacht: Man kann beim Online-Lesen einer Synopsis an vielen Stellen Test-Code einblenden und den Erfolg der Tests sehen, die den aktuellen Abschnitt der Spezifikation behandeln. So sind die korrekte Syntax und ihre aktuelle Implementierung gleichzeitig sichtbar.

Inzwischen ist die Test-Suite nicht mehr nur ein Validierungswerkzeug, sondern bildet die technische Spezifikation von Perl 6. Jedes Programm, das sie fehlerfrei ausfĂĽhren kann, ist ein legitimer Perl 6-Compiler.

Für das Ausprobieren von Pugs braucht man einen aktuellen Glasgow Haskell Compiler (GHC). Gibt es kein fertiges Binary der aktuellen Version für das eigene Betriebssystem, muss man die Quellen mit einer älteren Fassung des GHC übersetzen. Dem folgt die Klassikersequenz configure; make; make install für die aktuellen Haskell- und Pugs-Quellen. Danach liegt Perl 6 vor, das der Aufruf von pugs startet. Die FAQ im Wiki enthält ausführliche Anleitungen.

Millionen Code-Zeilen können nicht irren: Perl 5-Code ist nützlich und muss weiterleben. Eine anfängliche Idee, solche Programme mit dem Perl 5-Interpreter in Bytecode zu wandeln, den Parrot, die virtuelle Maschine für Perl 6, ausführt, haben die Entwickler inzwischen verworfen, die Umsetzung war unpraktikabel und ihr fehlte die Community-Unterstützung.

Inzwischen haben sich weitere Ideen entwickelt, wie beide Sprachen sich gegenseitig integrieren können. Beide Richtungen sind wichtig, weil Perl 5 mit CPAN eine mächtige Infrastruktur bereitstellt, die Perl 6-Programme nutzen können, und weil so Perl 5-Programme nach und nach Komponenten des Nachfolgers implementieren können, als würden sie eine fortgeschrittene CPAN-Bibliothek verwenden.

Die Perl 5-Runtime ist standardmäßig in Pugs eingebettet und führt nicht nur komplette Programme, sondern auch mit use v5; markierte Blöcke innerhalb von Perl 6-Anwendungen aus. CPAN-Bibliotheken lassen sich mit einer erweiterten use-Syntax einbinden:

use perl5:DBI;
my $dbh = DBI.connect('dbi:SQLite:test.db');
$dbh.disconnect;

Obwohl nicht immer alles reibungslos klappt, funktioniert es doch fĂĽr viele Module. Die Interaktion zwischen Perl 5 und 6 sowie die Verwendung verschiedener Komponenten aus anderen Projekten demonstrieren bereits, was den Entwicklern neben einer besseren Sprache vorschwebt: Bibliotheken verschiedener Skriptsprachen und unterschiedliche Paradigmen zu verschmelzen.

Im Entwicklungszweig von Perl 5.9 findet sich der von Larry Wall geschriebene und noch in Entwicklung befindliche Konverter MAD (Misc Attribute Decoration), der Perl 5-Code in den des Nachfolgers ĂĽberfĂĽhren soll.

Eine weitere Idee nutzt aus, dass Perl 5 schon lange .pmc- vor .pm-Dateien bevorzugt. Ursprünglich für Bytecode gedacht, lag diese Fähigkeit lange brach, war schon als veraltet markiert und wurde wiederentdeckt. Schreibt ein Compiler wie v6.pm Perl 5-Code in die .pmc-Dateien, kann Perl 5 sie in seiner gewohnten Umgebung ausführen, obwohl der Ursprungcode Perl 6 war. Beispiele finden sich in Module::Compile, einer beispielhaften Implementierung der Idee.

Zur Einführung in die Perl 6-Besonderheiten zeigt zunächst ein Beispiel (s. Listing 1) die Verwendung von Hashes und Arrays. Der Zugriff auf sie erfolgt jetzt konsistenter, und alle Werte sind Objekte, an denen sich Methoden aufrufen lassen. Das übernimmt wie in anderen Sprachen der Punkt. Die mit ihm bislang durchgeführte Stringverkettung erledigt die Tilde.

Mehr Infos

Listing 1

In Perl 6 ersetzt der Punkt -> beim Methodenaufruf, und ~ verkettet Strings.

my %tier = {
Affe => 0,
Tiger => 1,
Giraffe => 0,
};
# paarweise iterieren (Key/Value)
for %tier.kv -> $name, $raubtier {
say 'Obacht vor dem ' ~ $name if $raubtier;
}
# Unicode in Stringinterpolation
say "\c[BLACK SMILING FACE]";

Mit den neu eingeführten Meta-Operatoren lassen sich andere Operatoren beeinflussen oder erweitern. Praktisch sind zum Beispiel Hyperoperatoren: « beziehungsweise » modifizieren einen Operator so, dass er auf alle Elemente seiner Operanden wirkt. Deshalb erzeugt das Folgende ein Array mit den Elementen -1, -2 und -3:

my @foo = -« (1, 2, 3)

und die Addition zweier Listen lässt sich platzsparender schreiben:

my @rv = (1..4) »+« (1, 3, 5 ,7);

Zwischen die Elemente eines Arrays fĂĽgt der Reduce-Metaoperator einen Operator ein:

[+] 1, 2, 3;      # 1 + 2 + 3 = 6
my @a = (5,6);
[*] @a; # 5 * 6 = 30

Eine an die bisherige Syntax für Funktionen angelehnte Notation erlaubt die Definition eigener Rechenvorschriften. Mit dem Reduce-Operator lässt sich beispielsweise ein !-Operator (Fakultät) definieren:

sub postfix:<!> (Num $x) {
return [*] (1 .. $x);
}

Sogenannte Multisubs implementieren Polymorphie fĂĽr Funktionen. Welche der Varianten einer Funktion der Interpreter verwendet, ergibt sich aus ihren Aufrufparametern. Dadurch lassen sich zum Beispiel Abbruchbedingungen rekursiver Algorithmen elegant auslagern, wie die folgende Quicksort-Implementierung zeigt:

multi sub quicksort ( ) { () }
multi sub quicksort ( *$x, *@xs ) {
my @pre = @xs.grep:{ $_ < $x };
my @post = @xs.grep:{ $_ >= $x };
return(quicksort(|@pre), $x, quicksort(|@post));
}
say quicksort(1, 5, 2, 4, 3);

Das Beispiel demonstriert außerdem den Einsatz von „slurpy“ Funktionsparametern („*“ vor der Variablenmarkierung), die viele Argumente in einer Variablen aufnehmen. Ergänzend dazu verteilt der Funktionsaufruf das Array auf die Argumente (mit „|“), sodass beides zusammen etwa dem Perl 5-Verhalten entspricht. Denn sein Nachfolger verzichtet auf das bisherige Aufdröseln („flattening“) von Parametern.

Analog zum Aufruf von grep als Methode an den Array-Instanzen lässt sich quicksort wie folgt verwenden:

(1, 5, 2, 4, 3).quicksort.join('-').say;

wofür man unter Perl 5 noch das Modul autobox benötigte.

In Perl 6 heißt das switch-Statement given ... when. Das folgende Beispiel benutzt es in Verbindung mit dem „Fish-Operator“, der etwa dem früheren Diamond-Operator entspricht und von der Standardeingabe beziehungsweise der ersten Datei aus der Parameterliste liest:

print "Gib mir eine Antwort: j/n: ";
my $input = =<>;
given $input {
when "j" { say 'ja' }
when "n" { say 'nein' }
default { say 'bummer' }
}

Die Bedingungen in den when-Abschnitten sind Kurzformen des Smartmatch-Operators ~~. Er erweitert das Perl 5-Konzept des Match-Operators „=~“ für reguläre Ausdrücke so, dass praktisch alles mit allem verglichen werden kann und das Ergebnis von den Werten und ihren Typen abhängt. Listing 2 zeigt einige Beispiele. Der Smartmatch-Operator hat inzwischen zusammen mit anderen Neuerungen wie given ... when und say auch den Weg in Perl 5.9 gefunden.

Mehr Infos

Listing 2

Per Smart-Match lassen sich Daten nahezu aller Typen miteinander vergleichen.

# String vs. Regex
"yes" ~~ "y" # false
"yes" ~~ /y/ # true
# Hashes
my %hash1 = { tiger => 1, giraffe => 0 }
my %hash2 = { giraffe => 0, tiger => 1 }
my %hash3 = { tiger => 1, giraffe => 0, affe => 0 }
%hash1 ~~ %hash2 # true (Vergleiche Hashelemente)
%hash1 ~~ %hash3 # false
# OO
class Dog {}
class Cat {}
class Chihuahua is Dog {}
Chihuahua ~~ Dog # true (Chihuahua is a Dog)
Chihuahua ~~ Cat # false

Gather...take füllen ein Array direkt aus einem Block heraus und bieten damit mehr Flexibilität als das bisherige push. Die folgenden Zeilen liefern das Array [1, 3, 5, 7, 9, „Affe“, „Tiger“]:

my @res = gather {
for 1 .. 10 {
take $_ if $_ % 2;
}
for <Affe Tiger Fink Star> {
take $_ if /Tiger|Affe/;
}
};

Junctions vereinen mehrere Werte zur selben Zeit in einer einzigen Variablen und erinnern damit an Damian Conways „Quantum Superpositions“. Die Werte können quantifiziert sein: keiner, einer, einige, alle. Da Perl Junctions syntaktisch wie einzelne Werte behandelt, lassen sich zum Beispiel Mengenoperationen auf Operatoren für Einzelwerte zurückführen, und Perl 6 löst die möglichen Beziehungen parallel auf (s. Listing 3).

Mehr Infos

Listing 3

Mengenoperationen mit Junctions

# irgendein Wert aus einer Liste von Strings
my $tier = any <tiger giraffe ferkel affe>;
# stimmt ein einzelnes Element ĂĽberein?
say "Tigeralarm" if $tier eq 'tiger';
# stimmen einzelne Elemente
# zwischen beiden Listen ĂĽberein?
my $haustier = any <schwein ferkel maus>;
say "Haustier gefunden" if $haustier eq $tier;
# stimmt keines der Elemente der ersten Liste
# mit einem Element der anderen Liste ĂĽberein?
my @vogel = <amsel drossel fink star>;
say "Kein Vogel dabei" if none(@vogel) eq $tier;

Das neue Objektsystem ist sehr umfangreich. Listing 4 illustriert die Verwendung von Rollen (Roles; Interfaces mit Default-Implementierung). Aus Platzgründen kann es nur die Prinzipien der Perl 6-Objektorientierung veranschaulichen. Die dort verwendeten drei Punkte ... sind valider Perl 6-Code: Es handelt sich um den „yada, yada, yada“-Operator. Er lässt sich zwar übersetzen, schlägt aber bei der Ausführung fehl. Das Beispiel erzwingt damit ein Überschreiben der Methode in der Klasse, die die Role verwendet. Synopsis 12 erläutert das Gesamtkonzept ausführlich.

Mehr Infos

Listing 4

Perl 6 kennt Rollen und viele andere Konzepte der Objektorientierung.

# Interface und Sammlung von Vergleichsmethoden
role Comparable {
# spätere Implementierung erzwingen
method compare { ... }
method less_than ($other) {
$.compare($other) == -1
}
method equal_to ($other) {
$.compare($other) == 0
}
method greater_than ($other) {
$.compare($other) == 1
}
}
# Ein Interface ohne Default-Implementierung
role Printable {
# spätere Implementierung erzwingen
method to_string { ... }
}
# Eine Währungs-Klasse,
class US::Currency {
# Methoden aus obenstehenden Roles einmischen
does Comparable;
does Printable;
# numerisches Attribut mit
# Schreibrechten und Defaultwert
has Num $.amount is rw = 0;
method compare ($other) {
$.amount <=> $other.amount;
}
method to_string {
sprintf '$%0.2f USD', $.amount;
}
}
# --- main ---
# Instanziierung
my $income = US::Currency.new(amount => 9876.50);
my $cost = US::Currency.new(amount => 1000 );
# Verwendung
say $income.to_string; # $9876.50 USD
say $income.perl; # Objekt-Dump
say $income.greater_than( $cost ); # true
# Introspection / Metamethods, siehe S12
say $income.HOW.perl; # metaclass object
say $income.WHAT.perl; # ähnelt Perl 5 'ref'
say $income.isa( 'US::Currency' ); # true
say $income.isa( 'EU::Currency' ); # false

Grammars befinden sich noch in der Entwicklung. Einige Bestandteile wie Rules und reguläre Ausdrücke sind jedoch schon nutzbar. Man kann Grammatiken definieren, die aus einzelnen Rules bestehen und zum Schreiben von Parsern dienen können.

Listing 5 zeigt das Beispiel einer einfachen Grammatik für eine Minisprache, die einen Programmanfang, ein Ende und dazwischen durch Leerraum getrennte Kommandos definiert. Im Beispiel ist die umschließende grammar noch auskommentiert, weil der Code sonst nicht zuverlässig funktioniert - sie soll hier den Zusammenhang visualisieren.

Mehr Infos

Listing 5

Regeln definieren eine einfache Grammatik

#grammar Minilanguage {
rule start { START | BEGIN }
rule command { help | showversion | quit }
rule commands { [<command>]+ }
rule stop { STOP | END }
token prog {
<start>
<commands>
<stop>
}
#}
# verarbeitet das Match-Objekt
sub execute (Match $prog) {
say "Running...";
my $i = 0;
for split( rx/\s+/, $prog<prog><commands> ) -> $cmd
{
say " { $i++}: $cmd";
}
say "Done.";
}
my $example_prog = 'START showversion quit STOP';
my $parsed = $example_prog ~~ /<prog>/;
execute($parsed) if $parsed;
say $parsed.perl; # dump Match objekt

Jeder, der jetzt Perl 6 programmieren will, kann das mit Pugs tun. FĂĽr die Beteiligung am Pugs-Projekt sind keine Haskell-Kenntnisse erforderlich. Ein guter Start sind Portierungen eigener oder fremder Module nach Perl 6, die das Pugs-Repository aufnehmen kann.

Manchmal holpern Features noch, weil sich Dinge ändern, aber die sich daraus ergebenden Diskussionen oder Bug-Reports sind ein wichtiger Beitrag zum Projekt. Die Entwickler benutzen allerdings kein Bug-Reporting-Werkzeug, sondern vergeben Commit-Rechte an Fehler-Melder, die ihre Berichte in Form von Testfällen liefern. Ein Commit-Bit bekommt man auf Anfrage im IRC-Channel #perl6 auf irc.freenode.net innerhalb von Minuten, sofern jemand wach ist, der eine „Invitation“ senden kann.

Diese etwas ungewöhnliche, anarchische Form der Mitarbeit war von Anfang an Teil des Pugs-Projekts. Es will jede Form von Overhead vermeiden und „optimized for fun (-Ofun)“ sein. Das Verfahren gewährleistet in der Tat die einfache Mitarbeit aller Perl-Interessenten.

Steffen Schwigon
ist Diplom-Informatiker und arbeitet als Softwareentwickler bei webit! Gesellschaft fĂĽr neue Medien mbH.

Mehr Infos

iX-TRACT

  • Mit Pugs lassen sich heute schon viele fĂĽr Perl 6 vorgesehene Neuerungen ausprobieren.
  • Die nächste Version der Skriptsprache wird größere Ă„nderungen im Objektsystem und beim Funktionsumfang bringen.
  • Viele Perl-5-Module lassen sich in der neuen Umgebung weiterhin einsetzen.

(ck)