Perl 6
Perl 6 Tutorial Part 8: Revision 1

=head1 Perl 6 Tutorial - Teil 8 : Introspektion und Metaprogrammierung

Herzlich willkommen auch zum achten und letzten Teil dieses tiefschürfenden
Tutoriums für werdende Perl 6-Programmierer. Diesmal wird es sogar noch eine Ebene
tiefer gehen, zu den Interna der Sprache und wie sie befragt und verändert werden.
Da diese Bereiche stärker theorielastig sind und von Pugs und Rakudo bisher kaum
implementiert werden, besteht diese Folge aus mehr Text und weniger Beispielen
als üblich.

=head2 Wozu Metaprogrammierung?

Von Anfang an ging es bei diesem Projekt nicht nur darum vergangene Irrtümer
auszubügeln und den aktuellen Stand dynamischer Sprachen in vielem wieder ein- und
überholen. Perl 6 sollte auch nach längerer Reifung ebenfalls lange halten. Was
bedeutet, daß die Sprache fähig sein muß, künftige Anpassungen zu unterstützen,
ohne das die Implementation des Interpreters angefasst werden wird. Einfacher
gesagt: Perl 6 sollte bessere Möglichkeiten der Metaprogrammierung erhalten,
als die derzeit berüchtigten I<Sourcefilter>, die abgeschafft wurden, da sie viel
zu langsam und fehleranfällig sind, um in echtem Produktionscode eingesetzt zu werden.

Das ganze hat aber noch eine anderen Grund. Viele der weitverbreiteten Sprachen
haben etliche Varianten oder auch Ableger, da verschiedene Fachbereiche oder
Anwendergruppen verschiedene Betrachtungsweisen kennen oder bevorzugen.
Um die Aufsplitterung von Perl in Derivate wie es z.B. mit PHP geschah zu verhindern,
muß Perl noch wandlungsfähiger werden.
Und nur eine gemeinsame Basis macht eine Infrastruktur und damit alle Beteiligten
wirklich mächtig (siehe CPAN).
Gerade in den letzten Jahren wird das mit dem Modewort I<domain specific language>
(DSL) zusammengefasst, wenn Sprachen einer Aufgabenstellung angepasst werden können,
um diese effektiv und für die mit der Materie Vertrauten verständlich zu lösen.
Flexibilität (TIMTOWTDI) hatte sich Perl schon immer auf die Fahnen geschrieben
und dies wird mit Perl 6 nur noch etwas weiter getrieben. Perl 6 ist in Wirklichkeit
eine Metaprogrammiersprache, die sich zur Laufzeit in fast alles Denkbare verwandeln
kann. Dies ist in Ordnung so, mein Larry Wall, da alles erlaubt sein soll,
wenn man es vorher explizit deklariert. Und Lernende werden mit einem ausgewogenem
Satz vordefinierter Befehle zu einem "guten Stil" angeleitet, bis sie fähig werden,
alle Regeln zu verändern. Und je mehr sie lernen diese Regeln zu ihrem Vorteil
zu ändern, umso kürzer, effektiver und oft auch lesbarer werden ihre Programme.

Dieser Ansatz bringt nicht nur Freiheit für den Programmierer sondern erlaubt die
immer komplexer werdenden Softwaresysteme kompakter und beherrschbarer zu schreiben.
Ein Blick in die Java-Welt zeigt die Nachteile hierarchisch-logischer Strukturen,
die oft zu unbeweglich und überladen und damit schwer lesbar und veränderbar sind.
Aber selbst in der Welt von Perl und Ruby lernte man in den letzten Jahren, daß
die zuerst gepriesenen Web-Frameworks schnell sehr umfangreich werden. Jedes ist
eine Welt für sich, die erlernt sein will, obwohl sich viel Funktionalität ähnelt.
Deshalb entstanden in letzer Zeit mit Rack, Mojo und anderen Unterfangen Software,
die sich auf einzelne Probleme konzentriert, um diese in logischen Einheiten mit
eine möglichst einfachen API handhabbar zu halten. Die Idee dahinter ist nicht neu,
denn Perl 1.0 brachte Shellbefehle in die C-Syntax brachte, um komplexere Probleme
kurz und und dem menschlichen Denken nah zu lösen. Später erfüllten in Perl 5.n
gute Module den gleichen Zweck, nur das sie bereits von Programmierern der Sprache
zugefügt werden konnten, ohne den C-Quellcode von Perl anzufassen. Das neue Perl 6
ist eine logische Fortsetzung dieser Entwicklung zu einer Sprache die quasi "flüssig" ist,
und in verschiedene Situationen die Syntax annehmen kann, der dem Ideal des Benutzers
entspricht. Dadurch fallen Barrieren, die bisher durch Programmiersprachgrenzen
aufgestellt wurden. Große Systeme bestehen zunehmend aus Teilen die in verschiedenen
Sprachen und Versionen geschrieben wurden, die sich nur in Details unterscheiden
können, aber dennoch von zusätzlicher I<Middleware> "zusammengehalten" werden müßen.
Diese Teilsystem werden oft kaum mehr angefasst, da sie ausgereift, zuverläßig
aber nicht immer leicht wartbar und auch nicht leicht erneuerbar sind. Deshalb
wachsen diese Flickenteppiche zu ungeahnten Komplexitäten und es gibt bereits
Lehrstühle für Professoren, die Menschen beibringen wollen mit solchen Ungetümen
umzugehen. Besser wäre es doch, wenn solche Software gleich in Perl 6, vielleicht
auch Ruby oder Lisp geplant wird, um diese Komplexität nicht entstehen zu lassen.
Und selbst wenn "unberührbare" Software Teil einer Anwendung ist, kann eine sehr
anpassungsfähige Sprache Kommunikation mit wenig Programmieraufwand ermöglichen,
und Perl bleibt was es immer war: eine I<glue-language>.

=head2 Wo beginnt Metaprogrammierung ?

Wenn eine kleine I<sub> den verfügbaren Wortschatz erweitert, ist das bereits
Metaprogrammierung? Das kommt auf den Fall an. Subroutinen können dazu verwandt
werden etwas Ordnung in Spaghetticode zu bringen, was unter das Schlagwort
"strukturierte Programmierung" fällt. Da die Errungenschaften von C<if>, C<while>
und Routinen seit den 70er-Jahren Allgemeingut wurden, fällt der Begriff heute selten.
Wesentlich häufiger hört man dieser Tage dagegen das Wort "funktionale Programmierung".
So heiß ein Programmierstil der sehr abstrakt ist und daher in die Metaprogrammierung
hineinragt. Mark Jason Dominus beschreibt in "Higher Order Perl" genauer wie das
in Perl aussieht (Buchrezension in $foo Winter/2008). Das Buch ist wohl zum Teil
nach den "Higher Order Functions" benannt. Das sind Routinen die eine Aufgabe
sehr abstrakt lösen und deshalb sehr vielseitig einsetzbar sind. Viele Perlianer
verwenden bestimmt solche "Higher Order Functions", ohne den Begriff zu kennen.
Z.B. C<map> und C<grep> fallen in diese Kategorie. Aber mit Perl 5.0 konnte man
auch bereits selber solche Functionen schreiben, da schachtelbare Namensräume für
Variablen und Codereferenzen als Parameter anwendbar waren. Ähnlich der OOP war
in Perl 5 fast alles möglich, nur vieles jetzt einfacher. Eine wichtige Technik
in der funktionalen Programmierung ist z.B. das I<Currying>. Auf deutsch: das
Erstellen einer Codereferenz auf eine Funtion höherer Ordnung (einen Alias), bei
der bestimmte Parameter festgelegte Werte haben und nur die restlichen Parameter
bestimmt werden können. Also wenn eine Funktion C<potenz> beliebige Potenzen
berechnet(ja es ginge auch mit C<**>, aber folgender Code entspricht einem
funktionalem Lösungsansatz):

subset Num+ of Num where { $_ > 0 };
subset Num- of Num where { $_ < 0 };
subset Zero of Num where { $_ == 0 };

multi sub potenz (Num :$basis!, Zero :$exponent!) {
return 1;
}

multi sub potenz (Num :$basis!, Num- :$exponent!) {
return 1 / potenz( $basis, -$exponent);
}

multi sub potenz (Num :$basis!, Num+ :$exponent!) {
return $exponent * potenz( $basis, $exponent - 1 );
}

Ein Funktion C<quadrier> würde ich nun explizit wie folgt erhalten:

&quadrier := &potenz.assuming( exponent => 2 );
# alternative Schreibweise:
&quadrier := &potenz.assuming( :exponent(2) );

Die läßt sich wie erwartet aufrufen:

say quadrier(5); # ergibt 25

C<.assuming> (englisch für angenommen) drückt in einer Alltagssprache genau aus
worum es hier geht: C<quadrier> entspricht C<potenz>, unter der Annahme, daß der
Exponent 2 ist. Das C<:=> ist keine Zuweisung sondern ein I<binding>. P6 kennt
weder direkten Manipulation der Symboltabelle mit Typeglobs noch Referenzen (der
Schrägstrich (I<backslash>) erzeugt jetzt I<captures>, siehe Teil 6). Um einen
Alias auf den Inhalt einer Variable zu erhalten, bindet man diese Variable mit
C<:=> an eine Andere.

my $planeten = 7;
my $planety := $planeten;
$planeten = 9;
say $planety; # gibt 9
say "yes" if $planety =:= $planeten;

Das letzte Beispiel führt die Ausgabe aus, da die Variablen an das gleiche Objekt
gebunden sind. Ein Binden zur Kompilierungszeit mit der Schreibweise C<::=> mag
selten gebraucht werden, aber das Ausführen von Routinen zum frühstmöglichen
Zeitpunkt wesentlich öfter. In Perl 5 schrieb man dazu den Code in einen BEGIN-Block,
was weiterhin möglich ist, aber Perl 6 kennt noch ein anderes Konzept, das dem
ähnelt, aber weitaus mächtiger ist. Manche behaupten sogar, daß vor allem diese
Macros LISP mächtiger machen als alle anderen Sprachen, was in den 60er und 70er
Jahren auch gestimmt haben mag.

=head2 Die wunderbare Welt der Macros

Macros sind (wie angedeutet) Routinen, die beim Kompilieren ausgeführt werden.
Im Gegensatz zu einer C<sub>, ändern sie die Sprache, da sie statt eines Wertes,
einen AST (Baumstruktur, die als Zwischenform beim Kompilieren entsteht) liefern,
der beim Kompilieren anstelle jedes Macroaufrufes in den AST des Programmes
eingefügt wird, bevor die Ausführung beginnt.

In C ist das bei weitem einfacher. Hier sind Macros lediglich Textbausteine die
an die mit dem Makronamen markierten Stellen im Quellcode eingefügt werden, bevor
der Kompiler sein Werk beginnt. Die alten Perl 5-Sourcefilter taten ihr Unwesen
nach dem gleichen Prinzip, nur daß hier die volle Kraft der P5-Regex am Werke war.
Das kann subtile, schwer zu entdeckende Fehler erzeugen, da jeder Macroprozessor
blind für die Bedeutung der Sprachsyntax ist und der von ihm erstellte Quelltext
nirgends einsehbar ist. Perl 6-Macros haben standardmäßig (wie alle Blöcke) keine
Seiteneffekte auf die lexikalische Struktur der umgebenden Quellen.
So etwas nennt die Fachwelt: hygienische Macros. Folgende Makros sind hygienisch:

macro summe { 3 + 4 }
macro summe { '3 + 4' }
macro summe is parsed { 3 + 4 }

C<parsed> ist ein Trait von Routinen daß nur Macros wirklich benötigen, aber da
es default ist, kann es auch weggelassen werden. Und auch die Verwendung der
Macros ist denkbar einfach.

say 2 * summe; # 14
say 2 * summe(); # 14
say 2 * &summe(); # 14

Auch wenn alle Aufrufe C<14> ergeben, so sind sie nicht identisch, da der dritte
erst zur Laufzeit aufgelöst wird. Doch manchmal sind dreckige Macros genau was
man möchte und dann schreibt man.

macro summe is reparsed { 3 + 4 }

Wird dieses Macro im vorigen Beispiel aufgelöst, hieße das Ergebnis C<10>.
Denn dieses Macro gibt nur einen String zurück, der zusammen mit dem umgebenden
Quellcode compiliert wird. Dabei gilt die alte Regel: Punkt- vor Strichrechnung.
Da diese Funktionsweise derartige Probleme provoziert, hat sie die beschriebene
Kindersicherung und es wird standardmäßig von Macros ein AST zurückgegeben.
Aber auch außerhalb von Macros kann dies getan werden. I<quoting> mit dem Adverb
C<:code> (siehe letzte Folge) und ein C<quasi> vor geschweiften Klammern oder
Anführungszeichen kann das (I<quasiquoting>) bewirken.

return quasi { say "foo" };
return Q :code / say "foo" /;

Beide Zeilen liefern keine Referenz auf eine Routine sondern ein kompiliertes
Stück Programm. Folglich ist ein Macro eine zu C<BEGIN> ausgeführte Routine
deren Rückgabewert derart C<quasi> kommentiert (I<gequoted>) ist. Dies ist eine
sehr elegante Eigenschaft von Perl 6, daß jedes Sprachelement mit den Bordmitteln
der Sprache beschrieben werden kann. Deswegen kann die Syntax, so reichhaltig sie
auch scheinen mag, auf einen wesentlich kleineren Kern reduziert werden, aus dem
sich beliebige Sprachen aufbauen lassen. Deshalb gilt für Perl 6 was John Foderaro
einst über Lisp sagte: "es ist eine programmierbare Programmiersprache".
Weil Perl syntaktisch viel reichhaltiger als Lisp ist, daß nur Funktionsnamen,
Wertelisten und runde Klammern kennt, müßen Perl-Macros wesentlich mehr können,
um wirklich alle Sprachbestandteile erweitern zu können. Einen neuer Operator
(z.B. für die mathematische Fakultät-Funktion) wird so erzeugt:

macro postfix:<!> { * 1..$^n }
macro postfix:('!') { * 1..$^n }

Das gewählte Operatorsymbol kann auch in andere Klammern als den Spitzen gehüllt
sein, aber der Vorsatz "postfix:" ist entscheidend, da wir einen Postfix-Operator,
also einen nachgestellten Operator (wie in C<$p++>) definieren wollen. Um noch zu
bestimmen wo der neue Operator seinen Platz in der Vorrangtabelle hat, könnte man
hinzufügen:

macro postfix:<!> is equiv(&postfix:<++>) { ... }

Das könnte man auch mit C<is tighter> oder C<is looser> definieren. Dann bekäme
der Operator eine eigene Spalte in der Vorrangtabelle die jeweils über oder unter
dem angegebenen Operator liegt.

Diesem Schema entsprechend gibt es eine lange Reihe von Schlüsselwörter die jede
Art von Operator oder Schlüsselwort bezeichnen. Es lassen sich eigne Arten des
I<Quoting>, Regex-Befehle, Spezialvariablen oder neue sekundäre Sigils einführen.
Wenn jemand XML-Kommentare in seinem Perl haben möchte so reicht ein:

macro circumfix:«<!-- -->» ($text) is parsed / .*? / { "" }

C<$text> ist der Parameter, der den Text zwischen den Kommentarzeichen beinhaltet.
Nach dem C<is parsed> steht die Regex mit der geparsed werden soll. In diesem
Fall hat das <reparsed> den Vorteil, daß die Regex mit Regeln formuliert werden
kann die erst später definiert werden.

=head2 Da entlang Alice

Aber die Manipulation der Sprache kennt noch eine Ebene. Es ist sogar möglich
die Regeln zu ändern mit denen der Interpreter den Quellcode einliest. Seine
Arbeitsweise wird in der STD.pm mit der Hilfe von P6-Regex-Grammatiken definiert.
Wie diese formuliert werden und aufgebaut sind, behandelte die letzte Folge.

Intern wird die Sprache in mehrere Teilsprachen ("slangs") gegliedert.
Die Kernsprache ist z.B. eine Grammatikobjekt, auf das mit C<$~MAIN> zugegriffen
werden kann. Die Regeln für das Kommentieren stehen unter C<$~Q>.
Und die Regex die bestimmen wie Regex einzulesen sind, finden sich unter C<$~Regex>.
C<~> ist die Twigil dieser Sondervariablen. Und da Grammatiken nach außen normale
Objekte sind, können sie abgeleitet und verändert werden wie jedes andere Objekt
auch. Sämtliche Änderungen gelten nur für den aktuellen Namensraum (I<scope>)
und die I<defaults> können jederzeit wiederhergestellt werden, da sie unter
C<%?LANG> gespeichert sind.

=head2 Zeig mir dein Inneres!

Objekte können in Perl 6 sehr stark durchleuchtet und verändert werden. Jede
Elternklasse, jede Methode und jede Signatur (mit C<$obj.methode.signature>) kann
abgefragt oder verschiedenst geprüft werden. C<$obj.WHERE> nennt die Speicheradresse,
C<.WHAT> den Typ, was in etwa dem Befehl C<ref> in Perl 5 entspricht.

Es lassen sich mit Roles beliebige Methoden zu Laufzeit in ein Objekt einfügen,
aber der direkteste Weg dafür ist wohl:

augment slang Regex {
token regex_metachar:<^> { ... }
}

C<augment> (englisch für einblenden) fügt nur Regeln (Methoden) in die Grammatik
(Klasse) ein, soll eine gesamte Slangdefinition ausgetauscht werden, dann ist
C<supersede> (englisch für überlagern) das Mittel der Wahl.

Es gibt noch viele weitere Sondervariablen, wie für den umgebenden Block
(C<&?BLOCK>) oder die umgebende Routine (C<&?ROUTINE>) mit denen sich weit mehr
tun läßt als noch in Perl 5, z.B. lassen sich alle Sprungmarken im aktuellen Block
mit C<&?BLOCK.labels> auflisten. Auch C<&?ROUTINE.name> ist praktisch, wenn man
nicht merh weiß wo sich die Ausführung gerade befindet. Doch dies sieht man alles
in den Tabellen im Anhang B meines Wiki-Kompendiums, siehe Artikelende.

=head2 Was sich sonst noch tat

Zu Beginn dieses Tutorials kündigte ich an, mit jedem Teil auch alle Änderungen
bereits erwähnter Syntax zu dokumentieren. Doch zum Glück waren es weit weniger,
als angenommen, sodaß eine kleine Aktualisierung am Ende des letzten Teils genügt.
Gleich im ersten Teil wies ich auf eine potentielle Stolperfalle:

$b = =$a; # Zuweisung einer Zeile aus einem Datenstrom
$b == $a; # numerischer Vergleich

Diese ist mittlerweile behoben, denn der prefixe Operator C<=> wurde ersatzlos
gestrichen. Statt dessen sollte man die Methoden C<.lines> und C<.get> verwenden.
C<.lines> liefert einen Iterator und C<.get> tatsächlich Textzeilen.

my $name = "artikel.txt";
my $handle = $name.open err die "Kann '$name' nicht öffnen: $!";
my $ganzer_inhalt = $handle.slurp;

for $handle.lines > $zeile { ... }
while $handle.get
> $zeile { ... }
$zeile = $handle.get;
$handle.close;

Da der Kopf einer C<while>-Schleife im Skalarkontext und C<eager> evaluiert wird,
würde C<.lines> dort einen Array mit allen Zeilen liefern. Da jedoch C<for> den
Arraykontext forciert der standartmäßig C<lazy> ist, wird dort bei jeder Iteration
jeweils eine Zeile überwiesen. Im Skalarkontext würden alle Zeilen mit C<~>
verbunden geliefert.

Der neue Metaoperator C<R> kam hinzu. Er vertauscht lediglich die Operanden,
R steht für I<reverse> (englisch rückwärts).

say 3 R- 4; # sagt 1
say 2 R** 3; # sagt 9

Der Kreuz-Metaoperator wurde vereinfacht. Von nun an reicht es dem Operator ein
C<X> voranzustellen, vorher hieß das noch z.B. C<X~X>.

<a b> X~ <1 2> # <a1 a2 b1 b2>

Der Hyper-Metaoperator läßt sich aber nicht so vereinfachen, da die Pfeile
(<< und >>) bestimmen, wie der Operator auf Dimensionalität der jeweiligen Seite
reagieren soll.

Neben autogenerierten positionalen Parametern (erkennbar an der twigil C<^>), gibt
es nun autogenerierte benannte Parameter, deren Twigil C<:> sich nahtlos in die
Syntax zur Deklarierung benannter Parameter einfügt.

Selbstverständlich gab es noch weit mehr Änderungen, doch diese sind oft subtiler
oder liegen außerhalb der behandelten Themen. Einen tieferen Einblick gewährt das
Tutorial in der Wiki der deutschen Perl-Community unter
L<wiki.perl-community.de/cgi-bin/foswiki/view/Wissensbasis/PerlTafel>,
oder einfacher zu tippen: L<de.perl.org> und dann auf Wiki > Wissensbasis > Tutorials
klicken. Viele hilfreiche Texte beinhaltet auch die Seiten von Moritz Lentz unter
L<perl-6.de>. Dieses Tutorial wurde jetzt in eine Wiki online gestellt unter:
Lhttp://wiki.perl-community.de/cgi-bin/foswiki/view/Wissensbasis/Perl6Tutorial
und kann nun von jedem kommentiert und verbessert werden. Ich habe vor es weiter
zu verbessern und auch ins Englisch zu übersetzen, wo es in der Wiki der TPF
unter L[perl tablets] zu finden sein wird.


Upload Files

Click "Browse" to find the file you want to upload. When you click "Upload file" your file will be uploaded and added to the list of attachments for this page.

Maximum file size: 50MB

 
 
 
File Name Author Date Uploaded Size

Save Page As

Enter a meaningful and distinctive title for your page.

Page Title:

Tip: You'll be able to find this page later by using the title you choose.

Page Already Exists

There is already a page named XXX. Would you like to:

Save with a different name:

Save the page with the name "XXX"

Append your text to the bottom of the existing page named: "XXX"

Upload Files

Click "Browse" to find the file you want to upload. When you click "Add file" this file will be added to the list of attachments for this page, and uploaded when you save the page.

 
 
 
Add Tags

Enter a tag and click "Add tag". The tag will be saved when you save the page.

Tag: 

Suggestions: