Perl 6
Perl 6 Tutorial Part 5: Revision 4


Captures und Subroutinen

Willkommen zum fünften Teil dieses ausführlichen Perl 6-Tutorials, der sich nur
mit dem kleinen "sub"-Befehl beschäftigen wird und einige Dingen die dazu gehören.
Subroutinen oder Funktionen, die in Perl 6 immernoch mit C<sub> deklariert werden,
gehören zu den wichtigsten und grundlegendsten Techniken der Programmierkunst.
Es sind Teilprogramme die mit einem möglichst aussagekräftigen Namen aufgerufen
werden. Meist werden ihnen Werte übergeben und oft liefern sie auch ein Ergebnis
zurück. Jeder der schon etwas Perl kennt, hat bereits Ähnliches geschrieben wie
folgende Funktion, welche die Länge der Hypotenuse berechnet.

sub hypotenuse {
my ($a, $b) = @_;
sqrt( $a**2 + $b**2 );
}

Und die gute Nachricht für Lernfaule lautet: Dies ist vollständig gültiges Perl 6.
Menschen die jedoch gerade von C oder Java zu Perl wechseln, hätten die Subroutine
wohl so geschrieben:

sub hypotenuse ($a, $b) {
return sqrt( $a*$a + $b*$b );
}

Und die gute Nachricht für diese Neulinge lautet: Dies ist ebenfalls vollständig
gültiges Perl 6. Die zweite Lösung liegt näher an dem was die meisten anderen
Sprachen da draußen betreiben (entspricht häufig den Lesegewohnheiten) und sie
spart mühevolle Fingerbewegungen (ebenfalls ein Indikator ob etwas perlish ist).
Aber was genau wurde geändert? Folgen dem C<sub>-Namen runde Klammern, definiert
das eine Signatur (Liste der Parameter) und keine Prototypen mehr wie in Perl 5.
Diese Parameter (auch C<@>) sind keine lokalen Variablen und (wenn vorher nicht
anders deklariert) schreibgeschützt. Folgt keine Signatur, landen Parameter in
C<@>, wie aus Perl 5 gewohnt. TIMTOWTDI.

Geforderte Parameter

Signaturen helfen jedoch nicht nur Neulingen, sondern nehmen Jedem Arbeit ab.
Zum Beispiel kann C<sub hypotenuse> nur dann sinnvolle Ergebnisse erzielen, wenn
sie 2 Parameter bekommt. Im alten System der Parameterübergabe wäre es aufwendig
dies einzufordern. In Perl 6 braucht der Programmierer dafür gar nichts zu tun,
denn würde man die zweite Routine mit C<hypotenuse(5)> oder C<hypotenuse(2,3,4)>
aufrufen, gäb es eine dicke Fehlermeldung zur Kompilierungszeit. Wäre die C<sub>
wie im ersten Beispiel implementiert, würde der Kompiler lautlos schnurren wie
ein Kätzchen, auch wenn die Ergebnisse der Funktion nicht immer brauchbar wären.
Doch wenn schon die Parameter prüfen, dann auch auf den Datentyp. C<Num> ist ein
nativer Datentypen für Skalare und entspricht dem Zahlenbereich rationaler Zahlen.

sub hypotenuse (Num $a, Num $b) {
return sqrt( $a*$a + $b*$b );
}

Der Vorteil einer solchen Prüfung: die Fehlermeldung kommt, wenn Unbeabsichtigtes
passiert (hier bei nichtnumerischen Längenangaben), nicht erst Zeilen später,
wenn ein Folgebefehl versagt. Somit sind Ursachen wesentlich leichter auffindbar.
Und um konsequent zu sein sollte die C<sub hypotenuse> nur Zahlen größer als 0
zulassen, da sie mit physischen Längen rechnet. Dazu definieren wir den eigenen
Datentyp C<Num+> (analog zu Q+) als Untermenge von C<Num> wie folgend:

subset Num+ of Num where { $_ > 0 };

Perfekt wäre es, wenn auch der Typ des Rückgabewertes festgelegt werden könnte.
Das wirkt in diesem Beispiel vielleicht etwas übertrieben, aber Programme werden
so zuverlässiger. Diese Art der Programmierung, bei der der Compiler die Anzahl,
und Typen der Ein- und Ausgabe einer Routine prüft, wird "Design by Contract"
genannt und wurde zuerst durch die Sprache Eiffel bekannt. Da Perl 6 Motto heißt
"All your Paradigm's belong to us" heisst, ist das beschriebene auch hier möglich:

Num+ sub hypotenuse (Num+ $a, Num+ $b) { ... }
sub hypotenuse (Num+ $a, Num+ $b --> Num+) { ... }

Bla Bla Bla

Die "..." in den letzten Beispielen waren nicht nur Dekoration die andeutet,
daß an dieser Stelle der eigentliche Code später eingefügt wird. Auch der Perl 6
versteht es in dem Sinne. Der Interpreter geht davon aus, daß der Programmierer
nicht ohne Grund beginnt eine Subroutine zu schreiben und möchte ihm mit einem
freundlichen Kompilererror daran erinnern, daß im Falle:

sub hypotenuse; # oder
sub hypotenuse { }

etwas fehlt. Der geschickte Softwarearchitekt kann aber mit dem Operator namens
"yadayadayada" (zu deutsch bla bla bla) dem Interpreter mitteilen, daß er später
die Routine schreiben wird. Je nachdem ob er möchte das die jetzige Routine ein
fail, eine Warnung (C<warn>), oder einen Error (C<die>) liefert, kann er die
Schreibweisen C<...>, C<???> oder C<!!!> verwenden. Doch kehren wir zurück zum
ersten Beispiel.

Optionale Parameter

Manchmal braucht es aber Routinen, die je Situation unterschiedlich viele Parameter
bekommen. Wie deklarier ich das in der Signatur, ohne daß sich der Interpreter
beschwert? In dem ich den Paramtern ein Fragezeichen anhängt und sie damit als
optional markiert.

sub hypotenuse (Num+ $a, Num+ $b, Str $txt? --> Num+) {
my $h = sqrt( $a*$a + $b*$b );
say "$txt $h." if $txt;
return $h;
}

Man muß nur darauf achten, in der Signatur die optionalen Parameter nach den
Notwendigen zu positionieren, da alle Parameter in der Reihenfolge befüllt werden,
in der sie der Routine übergeben werden. Deshalb sollte auch die Reihenfolge der
optionalen Parameter sorgfältig überdacht werden, um sie von links nach recht von
mehr zu weniger wichtig zu sortieren. Der Befehl C<if $txt> wird auf keinen Fall
Probleme bereiten. Denn auch wenn kein Antwortsatz gewünscht ist und C<$txt> leer
bleibt, wird die Variable auf jeden Fall mit C<undef> initialisiert. Um andere
default-Werte festzulegen könnte man schreiben:

Num+ sub hypotenuse (Num+ $a, Num+ $b, Str $txt?) {
my $h = sqrt( $a*$a + $b*$b );
$txt //= 'Länge:
say "$txt $h.";
return $h;
}

oder

Num+ sub hypotenuse (Num+ $a, Num+ $b, Str $txt = 'Länge: ') {
my $h = sqrt( $a*$a + $b*$b );
say "$txt $h.";
return $h;
}

Da optionale Parameter auch an der Zuweisung erkannt werden, darf das Fragezeichen
im letzten Beispiel weggelassen werden. Das Gegenteil des Fragezeichens ist das
Ausrufezeichen (siehe dem Bedingunsoperator C<?? !!>) Deshalb werden notwendige
Parameter mit einem angehängten C<$var!> deklariert, was aber bei positionalen
Parametern nicht notwendig, da default ist.

Schlürfende Parameter

Manchmal ist es aber auch praktisch eine unbekannte Anzahl von Parametern in einem
Array zusammenzufassen. Dies erreicht man durch einen Stern als Präfix:

sub summe (*@a) { + @a }

Aber auch Hashes und Skalare aller Art dürfen als "slurpy" deklariert werden. Im
folgenden Beispiel implementieren wir Perl's map-Funktionen. Mit dem Unterschied,
daß bei unserem C<map> der anonyme Block an beliebiger Stelle stehen darf und der
der Interpreter anhand der Signatur die Parameter passend zuordnet.

sub map (&code, @werte) {
return gather for @werte -> $wert {
take $code($wert);
}
}

Würde die Sigantur C<(@werte, &code)> lauten, müsste der Block immer an letzter
Stelle übergeben werden.

Benannte Parameter

Wie angedeutet waren alle bisherigen Parameter positional, richten sich also nach
der Reihenfolge im Funktionsaufruf.

sub hypotenuse ($a, $b) {
sqrt( $a*$a + $b*$b );
}

Selbst bei einem Aufruf wie C<hypotenuse($b, $a)> würde der Inhalt der Variable
C<$b> in den Parameter C<$a> kopiert werden und analog Variable C<$a> in C<$b>.
Es gibt jedoch sehr gute Gründe Parameter manchmal direkt beim Namen anzusprechen.
Der Quellcode wird nachvollziehbarer und nicht jeder kann sich die Reihenfolge
von 12 Parametern für jede Routine oder Methode merken. Oft ist auch nicht die
Eingabe von jedem Parameter notwendig, aber wie sag ichs dem Computer, daß ich
diemal gerne die Parameter 3, 5 und 12 übergebe. Selbst die bereits vorgestellten
optionalen Parameter waren positional. Aus der Überlegung erschließt sich auch
warum in Perl 6 benannte Parameter per default optional sind. Wie bekannt kann
das angehängte C<!> sie erzwingen. Benannte Parameter erkennt man am Präfix C<:>.
Und sehr zu beachten: sie folgen den positionalen Parametern in der Signatur.

sub new(:$parent, :$ID, :$value :@size, :@pos, :@item, $:style)

Es gab Moment, da wünschte ich mir beim WxPerl-Programmieren Perl 6 wäre schon da
und z.B. die C<new>-Methode einer Combobox hätte eine solche Signatur. Weil beim
erzeugen des Widget sind bei mir nur die Eltern (Platz in der Objekthierarchie)
und der Sytle (Aussehen und Verhalten) wichtig. Die ID lass ich autogenerieren,
Größe und Position bestimmen die Sizer und Textwert und die Item werden bei Bedarf
gesetzt.

my $cb = Wx::Combobox.new( parent => $win, :style($style) );

So würde mir das gefallen. Zu Demonstrationszwecken enthält das letzte Beispiel
beide Paar-Schreibweisen. Sie wurden bereits in der vorigen Folge erläutert. Nur
ließe sich hier C<:style($style)> auch zu C<:$style> zusammenfassen.

Hat eine Routine keine Signatur, erhält sie ihre mt Namen zugewiesenen Parameter
aus C<%>, so wie C<@> nur die positionalen Parameter enthält. Verwendet man
Platzhalter-Variablen wie C<$^a> in Routinen ohne Signatur (obwohl die nur für
einfache Blöcke gedacht sind), erscheinen die Werte dieser Variablen nicht mehr
in C<%> oder C<@>.

Möchte man ein Paar als positionalen Parameter angeben, muß er in runde Klammern
gesetzt werden. Wie nachfolgend zu sehen, kann man die umschließenden Klammern
einer Signatur meist weggelassen werden.

# Aufruf mit einem positionalem Argument
Wx::Combobox.new (:parent<$win>), ;

Ich weiß auch: Tk und viele andere Module kennen heute bereits benannte Parameter.
Die Übergabe eines anonymen Hashes hat zumindest optische Ähnlichkeiten, Typen
und Anzahl der Geforderten Parameter werden dabei nicht geprüft. In Perl 6 könnte
man aber auch eine Routine mit einem Hash aufrufen. Damit Perl die Paare des Hashs
als Name und Wert benannter Parameter wertet muss der mit einem senkrechten Strich
dereferenziert werden.

Wx::Combobox.new( |%default );

"|" ist keine Sigil für einen Datentyp, so wie ein "&" für Codereferenzen steht,
es dient lediglich zum interpoliert in den Capture-Kontext, vergleichbar mit "@@",
daß für den bereits behandelten slice- oder auch multislice-Kontext steht.

Was zum $@%& sind Capture?

Von allen Neuheiten in Perl 6 fordern I<Capture> wohl am stärksten die Fähigkeit
sich neue Nervenverbindungen wachsen zu lassen, denn soweit mir bekannt, gibt es
nichts Vergleichbares in anderen Sprachen. Ein Capture ist ein Datentyp, der einem
Skalar zugewiesen wird und alle Parameter eines Routinenaufrufs speichern kann.
Da Signaturen sowohl positionale als auch benannte Parameter haben können,
erscheinen Capture anfangs als seltsame Hybride aus Array und Hash. Nur anders
als die Letztgenannten kann eine Capture nicht nachträglich verändert werden.
Sie ist "immutable" wie eine Liste. Weil es jetzt keine Referenzen mehr gibt
bekamen Capture den "\" vererbt, der von nun an "capture composer" heißt.

my (@a, $b, %c) = 1 .. 5, 6, {'sonnen' => 'schein'};
$capture = \(@a, $b, %c);

Diese Capture enthält keine Referenzen auf die Variablen sondern nur die Inhalte.

Anstatt zu referenzieren kann man in Perl 6 einen Alias auf eine Variable in der
Symboltabelle erstellen. Dies geht mit einem sehr einfachen Syntax und gänzlich
ohne Typeglobs.

$alias := $kathete;
$kathete = 5;
say $alias; # ist 5
# bindet während Kompilierung
$alias ::= $kathete;

Multi Sub's

Erinnern wir uns des ersten Beispiels. Wollte man eine Routine schreiben die die
Länge einer beliebigen Seite des rechtwinkligen Dreiecks berechnet, würden die
bisher vorgestellten Mittel nicht ausreichen. Mit benannten Parametern wäre die
richtige Zuordnung der Seiten gesichert, aber nicht die Forderung, daß 2 von 3
gegeben sein müssen. Eine Lösung bestünde darin das Problem aufzuteilen, was Perl
völlig neue Möglichkeiten eröffnet:

multi sub pythagoras (:$kathete!, :$kathete!) {
sqrt(@kathete[0]**2 + @kathete[1]**2);
}

multi sub pythagoras (:$kathete!, :$hypotenuse!) {
sqrt($hypotenuse**2 - $kathete**2);
}

Das Schlüsselwort C<multi> kündigt an, daß es mehrere Routinen gleichen Namens
gibt. Mit einem C<only> könnte ausschließen, das nachträglich noch eine C<multi>
zu einem Namen deklariert wird. Das ist aber meist nicht notwendig, da normale
C<sub> per default "C<only>" sind.

Wird die Routine mit C<pythagoras( :kathete<3>, :hypotenuse<5> );> aufgerufen,
prüft der Interpreter welche Signatur zu den Parametern passt. Manch einer ahnt
es schon. Auch dafür wird intern wie bei C<gigen/when> der "smartmatch" benutzt.
Es kann auch sehr praktisch sein selbst zu überprüfen ob ein Satz von Parametern
bei einer Routine Erfolg gehabt hätte.

$capture ~~ &routine.signature;

Parameter Traits

Alle bisherigen Paramter konnten in der Routine nicht verändert werden, was meist
sinnvoll ist, aber zuweilen unpraktisch. In diesen Fällen können einzelne Parameter
als veränderbar (rw steht für read/write) gekennzeichnet werden, was einer "<->"
Zuweisung in I<Pointy-Blocks> entspricht.

sub incr (*@vars is rw) { $_++ for @vars }

Der Befehl C<is> definiert I<Traits> (Charakteristiken) von Variablen. Das sind
neben dem Inhalt zusätzliche Werte oder Eigenschaften die zur Kompilierungszeit
Variablen gegeben werden können. Im Gegensatz dazu werden mit C<but> I<Properties>
(zusätzliche Laufzeiteigenschaften) bestimmt. Somit wird der alte Perl 5-Witz
"0 but True" lauffähiger Code.

Eine andere Möglichkeit veränderbare Parameter zu erhalten ist der Trait C<copy>.
Wie der Name aussagt sind solche Parameter veränderbare Kopien der übermittelten
Variablen.

Eingewickelte Routinen

Es gibt sogar Situationen da muß man eine Signatur rückwirkend anpassen. Unsere
großartige C<hypotenuse>-C<sub> könnte Teil eine Matematik-Bibliothek sein die
wir benutzen wollen. Sie kann sogar die Hypotenuse berechnen, wenn ein Winkel und
die gegenüberliegende Seitenlänge gegeben ist. Nur leider rechnet sie mit I<gon>
(Neugrad) und unser Programm mit I<Grad> (Altgrad). Die Bibliothek zu verändern
kommt nicht in Frage, da die Patches in jede neue fehlerreduzierte Version der
Bibliothek eingepflegt werden müssten. Zum Glück gibt es auch dafür in Perl 6
eine elegante Lösung.

sub hypotenuse($l, $winkel) {...}
$handle = &hypotenuse.wrap( { callwith( $^l, $^winkel/360*400 ) } );
# funktioniert einwandfrei
hypotenuse(2,20);
&hypotenuse.unwrap($handle);

Der letzte Befehl hebt die Umhüllung auf und es können selbstverständlich beliebig
viele Umhüllungen stattfinden. Sind irgendwelche andere Vor- und Nachbereitende
Tätigkeiten auszuführen und die Parameter sollen unverändert an die ursprüngliche
Routine weitergereicht werden, kann man statt C<callwith> auch C<callsame> nehmen.
Beide Befehle liefern die Ergebnisse der originalen Routine, die dann noch nach
Wunsch nachbereitet werden können.

Rückgabekontext

Nachbearbeitungen werden aber oft vermieden, wenn die Routine auf den Kontext
eingeht in dem sie gerufen wird. Das ist meist eine Signatur über alle Variablen,
denen das Ergebnis der Routine zugewiesen wird. Diese Signatur erhält man mit dem
Befel C<caller.want> und man könnte ohne Damian Conways Modul I<Contextual::Return>
schreiben:

given caller.want {
when :($) {...} # Skalarkontext
when :(*@) {...} # Arraykontext
when :($ is rw) {...} # Ein lvalue wird erwartet
when :($,$) {...} # 2 Werte werden erwartet
...
}

Für all das gibt es auch noch eine andere Schreibweise.

if want.item {...}
elsif want.list {...}
elsif want.void {...}
elsif want.rw {...}

Dieser Kontexte gibt es noch vieler mehr. Auch ist C<.want> bei weitem nicht die
einzigste Methode zur Introspektion, aber ich will hier kein Handbuch schreiben,
sondern nur einige Möglichkeiten andeuten. Eine vollständige Auflistung aller
Details soll das Tutorial in der Wiki unter
http://wiki.perl-community.de/bin/view/Wissensbasis/PerlTafel werden. In diesem
Beispiel wäre ein C<want> anstatt C<caller.want> ausreichend gewesen, aber würde
der C<return>-Befehl innerhalb eines Blocks stehen, wären C<caller.want> und
C<context.want> verschieden.

C<return> verlässt immer die innerste umgebende Routine. Wird lediglich gewünscht
den Block zu verlassen, empfiehlt sich C<leave> zu nehmen. Logischerweise wird nur
der Rückgabewert von C<return> gegen die Signatur der innersten Routine "gematcht".

Module und Scope

Was wäre Perl ohne Module. Deshalb bietet Perl 6 auch für Modulauthoren etliche
Verbesserungen zur Vorgängerversion. Da aber in diesem Bereich vieles noch nicht
in trockenen Tüchern ist, jetzt nur einige Grundzüge. Mit C<package> werden
weiterhin Namensräume definiert, für Namensräume mit zusätzlichen Eigenschaften
gibt es jetzt C<module>. Der Befehl C<module Name;> besagt, daß für den Rest der
Datei der Namensraum I<Name> gilt. Für mehrere Module in einer Datei schreibe man
C<module Name{ ... }>. So können Namensräume auch verschachtelt werden und mit
C<my module Name { ... }> Module sogar als lexikalisch lokal bestimmt werden.
Analog dazu dürfen auch Subroutinen jetzt mit C<my> lokal sein. Standartmäßig
entspricht aber weiterhin ein C<sub routine {...}> einem C<our sub routine {...}>.

Eine der Hauptfähigkeiten von Modulen ist das Exportieren von Routinen. Dazu
benötigt man kein C<use Exporter;> mehr, sondern markiert die entsprechenden
Routinen mit einer I<Trait> als C<is export>. C<sub>-Traits haben ihre Position
nach der Signatur. Module werden wie bekannt mit C<use> oder C<require> geladen,
jedoch wurden auch diese Befehle wesentlich mächtiger um einige Probleme zu lösen
die ein wachsendes CPAN mit sich bringt.

use Dog:<1.2.1>;

So fordert man z.B. eine spezielle Version an. Noch genauer wäre:

# bitte keine Version 1.2.7
use Dog:ver(1.2.1..^1.2.7);

Auch ein optionaler Mechanismus zur Authentifizieren von Autoren ist im Syntax
vorgesehen, jedoch noch nicht voll ausgereift.

Namensräume mit weitaus mehr Eigenschaften werden mit C<class> erzeugt. Die OOP
wird aber Stoff der nächsten Folge sein.


Previous Chapter | Overview | Next Chapter


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: