Perl 6
Perl 6 Tutorial Part 3: Revision 3

=head1 Perl 6 Tutorial - Teil 3 : Operatoren für Arrays

=head2 Gar keine Einleitung

Herzlich willkommen zum dritten Teil dieses Perl 6-Tutorials. Wie immer sind ein
Editor und Pugs dazu hilfreich und wir setzen den Besichtigung genau da fort, wo
wir letztens inne hielten: bei den Operatoren.

=head2 Arrays in Perl

Und wie angekündigt geht es in dieser Folge um Operatoren, die den Umgang mit
Arrays erleichtern. Hier hat Perl 6 sehr zugelegt. Zwar zeichnet sich Perl 5
durch sehr einfach und vielseitig verwendbare Arrays aus, die zudem mit 'map',
'grep' und 'for' sehr flexibel bearbeitet werden können (das gibt es natürlich
auch alles weiterhin), aber besonders funktionale Sprachen wie Haskell haben in
den letzten Jahren gezeigt, daß auf dem Gebiet weit mehr möglich ist. Und es war
auch die durch Pugs herbeigeführte, enge Zusammenarbeit von Haskell-Programmierern
und dem Design-Team, die auf diesem Gebiet deutliche Spuren im Perl 6-Syntax
hinterlassen hat. Darüber hinaus hat sich ebenso grundlegendes in der Schreibweise
der Arrays geändert, daß damit nichts zu tun hat.

Als Perl noch klein war und auf Larrys Wickeltisch lag, gab es nur einzelne Werte
und einfache Listen oder Hashes als Spielzeug. Um sie gut unterscheiden zu können,
bekamen sie als Erkennungssymbol die Sigil $, @ und %. Da aber Arrays und Hashes
auch einzelne Werte abliefert können und Hashes sogar Arrays von Werten, entschied
Larry, es sei besser in diesen Fällen die Sigil zu ändern, um immer zu sehen, ob
man es grad mit einen oder mehrere Werten zu tun hat. (Hashes sind ja in Perl 5
bloß Arrays mit gerader Länge.) Mit 7 Jahren erhielt Perl jedoch zur Einschulung
Referenzen. Das war gut, denn nun konnte es komplexe Datenstrukturen erzeugen,
was für größere Schulaufgaben wichtig ist. Aber damit verloren die veränderlichen
Sigils ihren ursprünglichen Sinn, denn "$fibo[7]" konnte nun auch (eine Referenz
auf) einen Array oder einen Hash zurückgeben. Und auch das Dereferenzieren mit
geschweiften Klammern wie bei "@a = @{$fibo[7]}" kann zu komplexen Ausdrücken
führen, die Anfänger verwirren. Deshalb haben Variablen in Perl 6 unveränderliche
Sigils. Zu deutsch: eine Arrayvariable beginnt immer mit einem '@'. Damit bleibt
wenigstens der Typ der Variable sichtbar. Zusätzlich vereinfacht das Dinge wie:

Perl 5: Perl 6:
@gerade = @{$zahlen[4]}; @gerade = @zahlen[4];
$zahl = $halde->345; $zahl = $halde[3;4;5];

Diese Beispiele zeigen: den Kontext bestimmt jetzt vor allem der Empfänger der
Daten. Erhält ein Array eine Arrayref, dereferenziert Perl 6 automatisch. Weist
man einem Array einen Skalar zu, hat er nur noch einen (neuen) Wert. Wird einem
Skalar ein Array zugewiesen, so wird er zur Arrayref (ich mein natürlich Capture),
die behandelt werden kann wie ein Array, auch wenn sie die Sigil $ hat. (Fast wie
ein Array in PHP.)

In Teil 2 wurde vorgestellt, daß ebenso Operatoren den Kontext bestimmen können.
Auch hier erzwingt "~" den Stringkontext, was der Aneinanderreihung der Inhalte
aller Elemente entspricht (selbstverständlich durch Leerzeichen getrennt). Und das
"+" fordert den numerischen Kontext, in dem ein Array die Anzahl seiner Elemente
liefert, in gleicher Weise wie Perl 5-Arrays im skalaren Kontext. Dafür gibt es
noch einen objektorientierten, expliziteren Syntax:

Perl 5: Perl 6:
scalar @gerade; +@gerade; # Elementenanzahl
scalar @gerade; @gerade.elems; # geht auch
$#rray; @rray.end; # letzter Index

Doch auch für das "~@a" wurde noch eine Schreibweise geschaffen, da innerhalb von
doppelten Anführungsstrichen Arrays und Hashes nicht automatisch expandieren. Dies
soll eine einfache Ausgabe von Mailadressen und Prozentangaben ermöglichen.

Perl 6:
say @namen;
say "meine Adresse: ich@namen.de";

Möchte man die Ausgabe der oberen Zeile innerhalb einer Angabe geht das nun auch
so:

Perl 6:
say "Die taoistischen Unsterblichen sind: @namen[]";

Auch die Ausgabe einzelner Arraywerte kann Umsteiger anfangs ratlos machen, denn
z.B. das vorletzte Element fordert man jetzt mit "@a[*-2]" an. Der Stern bedeutet
jetzt in fast jedem Kontext so etwas wie "alles", "unendlich", "irgendetwas" oder
"Grenzwert". Damit möchte ich aber die Einweisung in Perl 6-Arrays beenden, da
eigentlich die Operatoren das aktuelle Thema sind.

=head2 Füttern der Arrays

Das einfache füllen eines Arrays geht wie gewohnt:

Perl 6:
my @noten = ('Do', 'Re', 'Mi', 'Fa', 'Sol', 'La', 'Si');

Die runden Klammern können weggelassen werden, da das Komma jetzt arrayerzeugender
Operator ist. Sonst würde im nächsten Beispiel gerade mal "Si" in "@noten[0]" landen.

Perl 6:
my @noten = 'Re', 'Mi', 'Fa', 'Sol', 'La', 'Do', 'Si';

Aber so würde es wohl auch kein geübter Perlautor schreiben, denn es gibt ja qw().
Gibt es das noch? So etwas praktisches und nützliches wurde nicht nur behalten,
sondern auch in der Schreibweise vereinfacht.

Perl 6:
my @noten = <Re Mi Fa Sol La Do Si>;
my @tiere = << $baer $hase $igel >>;

Im vorigen Teil hatte ich ja schon erwähnt das es von "<>" noch eine Version gibt,
die interpoliert. Die Namensgebung lehnt an '' und "" an, wo auch die "doppelte"
Version interpoliert (Variablennamen mit ihren Inhalt ersetzt). Diese Schreibweise
ist auch praktisch wenn dem Array Werte eines Hashes zugewiesen werden.

Perl 6:
my @ausgaben = %rechnung< alfred hein peter >;
my @ausgaben = %rechnung<< @namen[] >>;

Hashschlüssel wurden zwar vorher auch oft nicht gequotet aber sowas tun nur Rüpel,
da barewords bekanntermaßen böse und hinterhältig sind und liebe Programmierer
immer strict verwenden. In Perl 6 hat man hier keine Wahl, da strictness default
ist, und es keine Bareword-Stringliteral mehr gibt.

Auch die altbekannten Rangeoperatoren kann man immer noch zum befüllen von Arrays
verwenden:

Perl 6:
my @ziffern = 0 .. 9; # 0,1,2,3,4,5,6,7,8,9
my @buchst ='a'..'z'; # <a b c d e f ... y z>

Ersteres kann man aber jetzt noch kürzer schreiben:

Perl 6:
my @einer = ^10; # 0,1,2,3,4,5,6,7,8,9

Ja genau, das Dach schließt das ihm Folgende als Grenzwert aus und die 0 wird
standartmäßig als untere Schranke angenommen. Dieses Konstrukt hat vor allem zwei
praktische Anwendungen. Im Skalarkontext kann man damit Bereiche definieren, deren
Schranken nicht zur Menge gehören, aber auch einfache for-Schleifen können so
noch um ein paar Anschläge kürzer geschrieben werden:

Perl 6:
3 ~~ 3^.. 7; # gibt Bool::False
for ^5 { ... } # iteriert 5 mal

Und da wir grad bei Schleifen sind: Wie oft wollten Perl 5-Programmierer die
Iteratorvariable in Sprüngen oder rückwärts zählen lassen. Dies geht nun alles
sehr einfach:

Perl 6:
for ^10:by( 2) {...}; # 0,2,4,6,8
for 4..1:by(-1) {...}; # 4,3,2,1

Selbstverständlich ist "by" kein Spezialbefehl für Schleifen, sondern kann immer
für Bereiche (Ranges) eingesetzt werden. Wem es überhaupt nicht gefällt, kann im
zweiten Beispiel auch auf reverse zurückgreifen, denn '4..1' evaluiert zu einer
leeren Menge oder einem leeren Zahlenbereich.

Perl 6:
for reverse 1..4 {...}; # 4,3,2,1

=head2 Es lebe die Faulheit

Die Menge die ein Perl 6-Array speichert, kann aber nicht nur leer sein, sondern
auch unendlich groß. Dafür braucht es nicht einmal unendlich viel RAM. Es reicht
vollkommen ihn wie folgt als unendlich zu bestimmen:

Perl 6:
@zahlen = 5 .. Inf; # von 5 bis Unendlich
@zahlen = 5 .. ; # das Selbe
@zahlen =
.. *; # aka -Inf .. +Inf

Inf (gibt es in den Varianten +Inf und -Inf) oder * steht hier für Unendlich und
es ist auch ein mathematisch korrektes Inf, daß Perl 6 zurückgibt wenn man eine
Division duch 0 versucht. Aber wie funktionieren die letztgenannten Beispiele?
Perl 5 hätte hier unsanft angemeldet "Range iterator outside integer range at ...",
Perl 6 expandiert jedoch den Array erst wenn er tatsächlich gebraucht wird und
dann auch nur bis zu dem benötigten Element. Diese "lazy evaluation" gehört zu den
Einflüssen funktionaler Sprachen wie Haskell in Perl 6, die ich zu Begin ansprach.
Sie bewirkt, daß eine "for"-Schleife, die über "@zahlen" iteriert, so lange läuft,
bis ein Sprungbefehl das Verlassen der Schleife erzwingt oder das Betriebsystem
aufhört zu arbeiten. Doch im Gegensatz zu Haskell gilt für Perl immer noch TIMTOWTDI.
Wer seine Listen sofort expandiert haben möchte, kann dies mit "eager" erzwingen.

Perl 6:
@zahlen = eager 5..*; # jetzt hängt das programm wirklich
$zahl = lazy "$text:" ~ berechne();

Im Gegenzug ist es auch möglich mit "lazy" die "lazy evaluation" zu erzwingen, wo
Perl per default "eager" evaluiert, wie bei einer Zuweisung in den Skalarkontext.
Es empfiehlt sich jedoch dabei aufzupassen, da die Variable oder die Routine in
dem Beispiel zu einem späteren Zeitpunkt andere Werte liefern können. Überhaupt
erfordert die "lazy evaluation" etwas mehr Aufmerksamkeit um genau zu erkennen,
wann etwas ausgeführt wird. Das folgende Beispiel filtert die numerisch geraden
Zahlen aus einem Array und überweist sie in einen zweiten Array. Der verwendete
feed-Operators dient der Weiterreichung von Arrayelementen um etwa Schwartz'sche
Transformationen und ähnliches explizit zu formulieren. Neu daran ist, daß die
Werte einzeln "@out" verlassen um sogleich das ganze Fließband an Befehlen zu
passieren und im "@in" zu verschwinden. Im dritten Beispiel würde die gerade Zahl
also zuerst halbiert und in "@in" gespeichert, bevor der nächste Wert aus "@out"
entnommen wird. Diese häppchenweise Arbeitsweise gab den feed-ops ihren Namen.
Das vierte Beispiel demonstriert, daß sie von links nach rechts zuweisen können
und daß sich die Taktstraße auch aus mehreren Quellen speisen kann. Wegen der
Schreibweise "==>>" folgt dem letzten Element aus "@out1" das erste Element aus
"@out2".

Perl 6:
@in = grep { $%2 } <== @out;
@in = grep { $%2 }, @out; # funktionsgleich
@in = map {$_ / 2} <== grep { $_%2 } <== @out;
@out1 ==>> @out2 ==>> grep { /\s/ } ==> @in;

Selbstverständlich unterbricht ein "eager" oder "sort" die "lazy evaluation" in
solchen Befehlsfolgen.

Befehle wie push, pop, shift und splice, die auch dafür geeignet sind, Arrays zu
füttern, werden bereits genügend in der perldoc beschrieben. Bis auf Nuancen hat
sich für diese Befehle nichts geändert, weswegen sie hier nicht erklärt werden.

=head2 Die Raubtierfütterung geht weiter

Wenn einem grad nichts einfällt, kann ein Array natürlich auch mit Wiederholungen
eines Wertes gefüllt werden:

Perl 6:
@affen = "gibbon" xx 5;
# mir ist doch noch was eingefallen
@affen = <gibbon makake> xx 3;

Anders als in Perl 5.10 unterscheidet "x" hier keinen Kontext. Es wiederholt, wie
in der vorigen Folge gezeigt, immer einen String und erzeugt einen meist längeren
String. "xx" wiederholt immer eine Liste, selbst wenn sie nur ein Element hat.
Die Ergebnisse werden dann auch, wie anfangs beschrieben, an den Kontext des
Empfängers angepasst. Was Perl 5 leider auch nicht kennt, ist das große "X", der
Kreuzoperator. Ohne Schleifen und Zusatzmodule können damit alle Kombinationen
der Elemente mehrer Arrays erzeugt werden. Wichtigste Regel ist hier dabei, daß
der linke Array vorrang hat, also zuerst alle Paare mit dem ersten linken Element
gebildet werden, dann mit dem zweiten usw.

Perl 6:
1,2 X 3,4 # (1,3),(1,4),(2,3),(2,4)

Wie das Resultat aufgelöst wird, hängt wieder vom Kontext ab, wobei es an der Zeit
ist, einen neuen, interessanten Kontext vorzustellen. Der "slice"-Kontext, erkennbar
an seiner Sigil "@@", verweist auf Arrays, die wiederum Arrayreferenzen enthalten.
Slice bedeutet soviel wie Arraystück oder Scheibe. Der einfache Arraykontext würde
einen flachen Array erzeugen. Oft möchte man das gerade nicht.

Perl 6:
$ 1,2 X 3,4 # \(1,3),\(1,4),\(2,3),\(2,4)
@ 1,2 X 3,4 # 1,3,1,4,2,3,2,4
@@ 1,2 X 3,4 # 1,3,1,4,2,3,2,4

Genauso kontextsensitiv ist auch der "zip" Operator, der ebenfalls aus bestehenden
Arrays neue zusammenstellen kann. Er wird "zip" oder "Z" geschrieben und aus der
zweiten Schreibweise läßt sich auch sein Vorgehen ableiten. (Man nehme zuerst ein
Element aus dem linken Array, dann das erste aus dem rechten. Dann wieder zum
ersten und zurück ... ). Zip heißt zu deutsch Reißverschluss, was den Mechanismus
auch gut beschreibt.

Perl 6:
$ 1,2 Z 3,4 # \(1,3),\(2,4)
@ 1,2 Z 3,4 # 1,3,2,4
@@ 1,2 Z 3,4 # 1,3,2,4
% 1,2 Z 3,4 # { 1 => 3, 2 => 4 }

Besonders für die parallele Verarbeitung mehrer Arrays in einer Schleife ist das
sehr praktisch. Dies zeigt allerdings die nächste Folge des Tutorials, weil es
noch Wissen zu den inneren Verhaltensweisen anonymer Blöcke verlangt, was weit
außerhalb des heutigen Themas liegt.

Sowohl der Kreuz- als auch der Reißverschlussoperator kann mehrfach verkettet
werden, die Ergebnisse werden lediglich entsprechend komplexer.

Perl 6:
1,2 X 3,4 X 5,6 # (1,3,5),(1,3,6) ...

=head2 Giga, Hyper, Meta ...

Mit dem großen Kreuz lässt sich sogar noch weit mehr anstellen wenn es circumfix
(umschließend wie Klammern) um einen anderen Operator gesetzt wird. Dies eröffnet
das völlig neue Feld der Metaoperatoren für Arrays. Operatoren für Skalare können
somit auf Listen angewendet werden und für die Fülle der neuen Möglichkeiten muß
niemand lange Listen an neue Befehlen lernen. Es reicht völlig die Arbeitsweise
der drei Metaoperatoren zu verstehen.

Der Kreuz-Metaoperator funktioniert auch fast wie der einfache Kreuzoperator. Auf
jedes Kombinationspaar wird lediglich der rechte Operator angewendet. Am Beispiel
ist das leicht nachvollziehbar:

Perl 6:
<a b> X~ <1 2> # <a1 a2 b1 b2>
<1 2 3> X* <2 3> # <2 3 4 6 6 9>

Der praktische Nutzen von solchem Code ist nicht nur daß er mindestens 5 Zeilen
Perl 5 ersetzt, sondern daß er dem Interpreter auch erlaubt die Arbeitsschritte
auf mehrere Prozessoren(kerne) zu verteilen, da es auch nicht darauf ankommt in
welcher Reihenfolge die Werte berechnet werden, solang sie in der erwarteten
Reihenfolge ankommen. Genau diese Parallelisierung der Datenverarbeitung erlaubt
auch der nächste Metaop: der Hyperoperator. Er besteht aus zwei ">" oder "<", kann
aber auch mit den UTF-Symbolen namens Chevron geschrieben werden, welche genauso
aussehen. Wie zu erwarten, war das Gegenstand kräftiger Diskussionen, während der
Larry Wall seinen Standpunkt verteidigte: "UTF ist schon lange Standart und man
solle die Verwendung von UTF wenigstens ermöglichen. Alle "UTF-Operatoren" sind
ohnehin optional und Perl 6-Quellcode wird intern eh immer als Unicode angesehen."

Der klassische Anwendungsfall für Hyperoperatoren besteht darin, die ersten Werte
zweier Arrays als Operanden zu verwenden und das Resultat als ersten Wert im
Ergebnisarray zu speichern. Nächste Operanden sind die jeweils zweiten Werte u.s.w.
Sind beide Arrays unterschiedlich lang wird mit leeren Werten aufgefüllt

Perl 6:
@reste = <5 3 7> >>%<< <2 7 5>; # <1 3 2>
@summen = <2 3 4> >>+<< 1; # <3 3 4>

Möchte man einen einzelnen Wert auf eine Liste anwenden, so reicht es, die Richtung
des Hyperoperators umzukehren.

Perl 6:
<5 3 7> >>+>> 1; # <6 4 8>

Das breite Ende der Pfeile zeigt an, welche Seite die Dimension des Ergebnises
bestimmt. Hätte das letzte Beispiel Variablen benutzt, ließe es sich aber noch
elegant verkürzen.

Perl 6:
@a = @a >>+>> 1;
@a >>+=>> 1; # dito
@a >>++; # noch kürzer

Sehr nützlich ist auch, daß Hyperoperatoren sehr gut mit mehrdimensionalen Arrays
umgehen können.

Perl 6:
-« [1, 2, 3] # [-1, -2, -3]
[1, 2, 3] »+» 4, 5, 6 # [5, 7, 9]
[1, 2, 3] «+» 4, [5, 6]
# == [1,2 «+» 4, 3 «+» 5, 6] == [5, 6, 8, 9]

Im zweiten Beispiel bestimmt die linke Seite, wegen der Richtung der Operatoren,
die Dimensionalität. Beim dritten ist es keine Seite und so wird auch jedes slice
als Element angesehen und entsprechend "ausmultipliziert".

Die dritte Klasse an Metaoperatoren sind die Reduktionsoperatoren. Ihr Name ist
redlich verdient, da sie die Dimension ihres Operanden reduzieren, oder zu deutsch:
Sie machen aus einem Array einen Skalar, indem sie alle Arrayelemente miteinander
anwenden. Reduktionsoperatoren werden in eckigen Klammern geschrieben.

Perl 6:
+ 1..4 # 10 = 1+2+3+4
~ 'a'..'f' # 'abcdef'

Trickreich und effektiv lassen sie sich auch mit Vergleichs- und Auswahloperatoren
kombinieren.

Perl 6:
$sorted = < @a # wahr wenn sortiert
$wert = || @a # erster nicht leerer Wert im Array
$min = min @zahlen # @zahlen.min ginge auch
$min, $max = minmax @zahlen

Aber auch Reihen, wie man sie beim Mathe-Wettbewerb "Euler" häufig braucht,
lassen sich mit einer weiteren Schreibweise der Reduktionsoperatoren sehr knapp
formulieren.

Perl 6:
\* 1..3 # 1, 2 = 1*2, 6 = 2*3
\+ ^10 # 0,1,3,6,10,15,21,28,36,45

Diese Form liefert die Aufzählung aller Zwischenergebnisse eines Reduktionsops.
Formal hieße sich das ungefähr als "@ergebnis[$n] = @ergebnis[$n-1] op @input[$n]"
ausdrücken. Und auch an dieser Schreibweise erkennt man die Regel einer Huffman-Kodierung
(Je seltener etwas menötigt wird, desto länger (seltsamer) ist sein Name).

=head2 Und noch ein Blick in die Quantenwelt

Neben Larry Wall ist Damian Conway einer der Hauptgestalter der Perl 6-Syntax.
Nicht nur die ableitbaren Grammatiken der "rules" basieren zu einem guten Teil
auf seinem "Parse::RecDescent", auch sein Modul "Quantum::Superpositions" ließ
viele Perl-Programmierer erste Bekanntschaft mit Junctions schließen. Wie vieles
wofür sich Damian begeistert, können auch Junctions als abgehoben und schwer
zugänglich erscheinen. In funktionalen Sprachen wie Haskell erwiesen sie sich
jedoch als hilfreich, um komplexe logische Verknüpfungen einfacher zu formulieren.
Würde man in Perl 5 z.B. noch schreiben:

Perl 5:
if ($farbe eq "rot" or $farbe eq "blau" or $farbe eq "grün") {
print "Sie wählten eine Grundfarbe, interessant.\n"
}

So geht das in Perl 6 auch junktiv.

Perl 6:
if $farbe eq "rot" | "blau" | "grün" {
say "Sie wählten eine Grundfarbe, interessant."
}

Aus Perl 5 ist bekannt, daß "|" für ein logisches "oder"(or) steht, "&" für "und"
(and) und "^" für "entweder oder" (xor) steht. Neu ist, daß man den Sachverhalt:
"rot oder blau oder grün" in einer Variable speichern kann. Das letzte Beispiel
ginge damit so:

Perl 6:
my $gfarben = "rot" | "blau" | "grün";
if $farbe eq $gfarben ...

Wem das nicht ganz geheuer kann auch einen vertrauten Array verwenden und dessen
Elemente junktiv verknüpfen.

my @gfarben = <rot blau grün>;
if $farbe eq any(@gfarben) ...

Sicher ließe sich anstatt "any(@gfarben)auch die reduktive Variante @gfarben"
verwenden, allerdings ist ersteres weit eingängiger (und aus Quantum::Superpositions
bekannt). In gleicher Weise entspricht das "alleinem" (and), "onedem"
(xor) und "none" einem "not".

Perl 6:
if all(@tiere) ~~ all(@affen) { say "nur affen hier ...
if all(@tiere) ~~ all(@affen) | $faultier { ...
if schau() | vergleich() eq any(@gfarben) { ...

Selbstverständlich lassen sich Junctions auch schachteln. Mit ihnen kann man auch
Operationen auf mehrere Werte parallel anwenden, ähnlich den Hyperoperatoren.

Perl 6:
my $gfarben = "rot" | "blau" | "grün";
$gfarben ~= 'e'; # "rote" | "blaue" | "grüne";
(1|2) + (3&4); # (4|5) & (5|6)

Am schönsten daran ist wohl, daß man so komplexe logische Regelwerke kombinieren
kann und Perl alle Details überläßt.

=head2 Danke

Über Arrays in Perl 6 lassen sich noch viele weitere Neuerungen berichten. Dieses
mal ging es vor allem um die damit zusammenhängenden Operatoren. In der nächsten
Folge lautet das Thema Kontrollstrukturen. Es wird daran anknüpfen, da Schleifen
oft für die Bearbeitung von Arrays und Hashes eingesetzt werden.


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: