|
Perl 6
Perl 6 Tutorial Part 8: Revision 4
{toc: }
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. ^^^ 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>. ^^^ 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. ^^^ 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. ^^^ 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. ^^^ 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. ^^^ 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: L<http://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. ---- "Previous Chapter"[Perl 6 Tutorial Part 2] | "Overview"[Perl 6 Tutorial] | "Next Chapter"[Perl 6 Tutorial Part 4] |