Eine der auf Neulinge gelegentlich abschreckend wirkenden Eigenschaften von Perl ist neben den kryptischen eingebauten Variablen die große formale und syntaktische Freiheit in der Quellcodegestaltung. Diese Freiheit ermöglicht es fortgeschrittenen Programmierern, erstaunlich kompakten Code zu schreiben. Außerdem führen in Perl viele Wege zum Ziel. Dies ist in der Abkürzung TIMTOWTDI zusammengefaßt: There Is More Than One Way To Do It. Wie viele Wege es wirklich gibt, auch einfache Dinge in Perl auf überzeugende Weise zu komplizieren, wird in der JAPH-Sammlung dokumentiert. Die dortige Aufgabe ist, den Text Just Another Perl Hacker auszugeben. Die bizarrsten Ideen werden in einer eigenen Datei namens JAPH (Just Another Perl Hacker, zu finden auf CPAN:/misc/) gesammelt. Diese Sammlung eignet sich nicht nur zum Studium von Verfahren, die man besser nicht anwendet, sondern enthält auch ein paar wirklich überzeugende Beispiele bestimmter Funktionen und Programmiertechniken.
In den folgenden Beispielprogrammen werden im Gegensatz zu JAPH nur praktikable und nachvollziehbare Lösungen angeboten. Es kann allerdings durchaus sein, daß mehrere Wege nach Rom führen, oder sich mehrere Lösungsansätze anbieten. Dies kann unerheblich sein, so lange nur Vorlieben und Abneigungen des Programmierers eine Rolle spielen, kann aber auch ernsthafte Konsequenzen im Laufzeitverhalten oder der grundsätzlichen Logik des Programmablaufs mit sich bringen.
Es ist besonders hervorzuheben, daß Perl ein deutlich unterschiedliches Laufzeitverhalten für verschiedene Operatoren und Operationen aufweist. Es empfiehlt sich immer, nach einem für Perl idiomatischen Weg zu suchen, da dieser meistens signifikant schneller abgearbeitet wird. Das folgende einfache Beispiel benutzt entweder eine konventionelle Schreibung der Addition zweier Zeichenketten oder aber den Perl-typischen Operator.
#!/usr/bin/perl # Ein Laufzeittest for ($i=0; $i<=50000; $i++) { # Konventioneller Weg, zwei Strings zu verknüpfen # $zeichenkette=$zeichenkette."a" # Perl-idiomatischer Weg zur Lösung der gleichen Aufgabe $zeichenkette.="a" }
Hier sehen wir Abarbeitungszeit und Systemauslastung des Programms, wenn der konventionelle Weg beschritten wird:
$ time ./run.pl
67.85user 0.07system 1:09.43elapsed 97%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (197major+59minor)pagefaults 0swaps
Hier sehen wir Abarbeitungszeit und Systemauslastung des gleichen Programms, wobei allerdings hier der idiomatischere Weg beschritten wird:
$ time ./run.pl
0.54user 0.01system 0:01.14elapsed 48%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (197major+37minor)pagefaults 0swaps
Der Unterschied (eine Sekunde statt einer Minute Laufzeit bei
halber statt voller CPU-Auslastung) ist erstaunlich groß, wobei ja
in der ganzen Laufzeit von einer Sekunde bereits der Aufruf des
Perl-Interpreters und die Laufzeitkompilation enthalten sind. Es
kann durchaus sein, daß der konventionelle Programmierstil bei
kleinen Datenmengen, wie sie typischerweise zur Validation eines
Programms benutzt werden, zu scheinbar vorzeigbaren Ergebnissen
führt, da diese ja korrekt sind. Mit kleinen Datenmengen läßt
sich aber keine Aussage zum Laufzeitverhalten machen. Erst mit
,,echten'' Datenmengen stellt man dann fest, daß das Programm
Antwortzeiten produziert, die es für den vorgesehenen Zweck
völlig unbrauchbar machen können. Im obigen Beispiel wächst
die Laufzeitdifferenz exponentiell mit der Datenmenge; wenn für
$i
ein kleiner Maximalwert (z. B. 1000) eingesetzt wird,
ist kaum ein Unterschied zu spüren. Wird aber statt der 50000
unseres Beispiels ein noch größerer Wert eingesetzt, so wachsen
die Antwortzeiten ins Uferlose.
Wenn sich das Problem stellt, aus einer lateinischen Transliteration
beispielsweise einen kyrillischen Text zu generieren, so kommt man
nicht umhin, der Behandlung von Digraphen besondere Aufmerksamkeit
zu widmen. Während für einfache 1:1-Ersetzungen die Funktion tr
völlig ausreicht, kann man mit ihr keine Texte übersetzen, in denen
mehrere Buchstaben des Transliterats für einen Buchstaben des Ziels
stehen. Man kann aber mit einer Reihe von Substitutionen arbeiten.
Wenn im lateinischen Text s
und sh
auftreten,
die für distinkte kyrillische Buchstaben stehen, so könnte ein
Ansatz so aussehen:
s/sh/<Kode für sh>/g; s/s/<Kode für s>/g;
sh
mehr im
Quelltext finden und der Zieltext wäre verfälscht.
Ein weiteres Problem dieses Ansatzes ist es, daß die Datenräume von Quelle und Ziel nicht voneinander getrennt sind. In dem Maße, wie die Substitutionen für jeden einzelnen Buchstaben des Alphabetes vollzogen werden, verschwindet der Quelltext aus der Variablen, bis er schließlich nicht mehr vorhanden ist. Er könnte zwar vor der Substitutionsoperation in einer anderen Variablen gesichert werden, aber prinzipiell wirft der hier vorgestellte Ansatz Probleme auf, die bei komplexen Programmen zu unübersehbaren Schwierigkeiten führen können.
Sinnvoller ist es daher, Funktionen zu schreiben, die den Quelltext unangetastet lassen und einen neuen Wert zurückgeben:
$neu=transliteriert($alt); sub transliteriert { # Funktion wird definiert my $eingabe=$_[0]; # Parameter liegen in @_ # Kommandos # Hier wird gearbeitet... $transliteriert=$irgendein_lokaler_wert; # Rückgabe }