In diesem Abschnitt befassen wir uns mit der Strukturierung von PERL-Code. Wir werden behandeln:
Bisher wurden in diesem Kurs PERL-Skripte behandelt. Wo aber ist der Unterschied zwischen einem "Skript" und einem "richtigen Programm" anzusetzen, wodurch unsterscheidet sich eine "Skriptsprache" von einer "wirklichen" Programmiersprache? Für den Schreiber dieser Zeilen unterscheiden sich wirkliche Programmiersprachen von Skriptsprachen dadurch, dass sie zur besseren Strukturierung und Wiederverwendung von Code Unterfunktionen bzw. Unterprogramme unterstützen, sowie durch die Möglichkeit Sammlungen solcher Funktionen in seperate Codebibliotheken auszugliedern und wiederzuverwenden. PERL unterstützt natürlich beide Features.
In PERL werden, wie in C Unterprogramme ohne Rückgabewert genauso definiert wie Unterprogramme mit Rückgabewert, nur dass eben am Ende der Funktion kein Wert zurückgeliefert wird. PERL macht also, anders als PASCAL keinen feinsinnigen Unterschied zwischen Funktionen und Prozeduren . Die einfachste Funktion sieht folgendermassen aus:
sub trivialfunktion {}
Die Definition der Funktion wird durch das Schlüsselwort sub (Subroutine) eingeleitet. Anschließend folgt der Name der Funktion (hier: trivialfunktion ). Schließlich folgt ein duch geschweifte Klammern eingegrenzter Block, der die Anweisungen enthalten könnte, hier aber trivialerweise leer ausgefallen ist. Die triviale Funktion kann nach ihrer Definition wie eine eingebaute PERL-Anweisung mit ihrem Namen aufgerufen werden:
trivialfunktion;
Da Funktionen mit leeren Anweisungsblöcken langweilig sind, hier eine Funktion die alle Werte des universellen Arrays @_ auf die Konsole schreibt:
sub myfunc {
print( join ("\n" ,@_ ));
}
Die Werte von "@_" können beim Aufruf der Funktion durch explizite Angabe in runden Klammern initialisert werden. So erzeugt der Aufruf:
myfunc(1,2,3,4,5);
Die Ausgabe
1
2
3
4
5
Dies ist die einfachste Möglichkeit Funktionen mit Parametern zu versorgen. Typisch für PERL ist die Möglichkeit die Funktion mit einer beliebigen, nicht von vorneherein festgelegten Anzahl von Parametern aufzurufen, was für Anhänger streng typisierter Sprachen oft etwas gewöhnungsbedürftig ist. Diese Aufrufkonventionen sind aber dem Herkommen von PERL als Sprache zur Manipulation von Texten bzw Textlisten geschuldet, und man sollte sich vergegenwärtigen dass sich die eingebaute "print" Funktion in PASCAL, dem Inbegriff einer streng typisierten Sprache, ebenfals mit einer beliebigen Liste von Argumenten aufrufen lässt. PERL erlaubt dasselbe Verhalten eben auch für nicht eingebaute Funktionen, und lässt dem Programmierer eine lange Leine um zusätzlichen Freiraum zu nutzen, oder sich darin zu verwickeln....
Die Parameter werden aus dem Array "@_" übernommen.
Dieses Array wiederum kann auf zweierlei Arten manipuliert werden:
Entweder direkt durch einen Ausdruck der Form
@_=<Liste>
oder dadurch, dass die Parameter nach dem Funktionsaufruf als explisite Liste
angegeben werden, wobei diese Liste übersichtlich in runden Klammern
angegeben werden kann, aber nicht muss.
Der Aufruf von myfunc im vorangangenen Beispiel hätte dann einfach
myfunc 1,2,3,4,5;
gelautet.
Eine weiteres PERL-Spezialität sind Referenzen auf Funktionen .
Dabei wird, wie bei Referenzen auf Skalare, Listen und Hashes ein Skalar benutzt,
um eine Funktion zu referenzieren. Mittels eines Ampersands "&"
kann der Skalar dann dereferenziert werden:
sub myfunc {
print( join ("\n" ,@_ ));
}
$variable="myfunc";
&$variable (1,2,3,4,5);
Referenzen auf Funktionen bieten eine syntaktisch einfache Möglichkeit erst zur Laufzeit eines Programmes zu entscheiden, welche Funktionen auf eine vorgegebene Menge von Parametern angewendet werden sollen, indem der Name einer auszführenden Funktion einer Variable zugewiesen wird.
Das Script funcref.pl demonstriert noch einmal alle Möglichkeiten Funktionen aufzurufen:
#!/usr/bin/perl
# Funktionsdefinition
#
sub myfunc {
print @_;
}
#
print "\n Aufruf mit expliziten Parametern (als geklammerte Liste): \n\n";
# (1a)
myfunc(1,2,3,4);
#
print "\n noch ein Aufruf mit expliziten Parametern \n";
print "(als nicht-eingeklammerte Liste): \n\n";
# (1b)
myfunc 1,2,3,4;
#
print "\n",'Parameter in @_ uebergeben : '," \n\n";
# (2)
@_=(1,2,3,4,5);
&myfunc;
#
#
print "\n indirekter Aufruf (als Referenz) mit expliziten Parametern \n\n";
$funcname='myfunc';
& $funcname
Eine wichtige Anwendung von Funktionsreferenzen unter Unix-artigen Betriebssystemen ist das Handling von Signalen.
In einer Unix-Umgebung werden Signale zur Kommunikation zwischen Betriebssystem und
Prozessen oder auch zur Kommunikation der Prozesse untereinander benutzt.
Das Signal "INT" (interupt) signalisiert zum Beispiel eine (wie auch immer geartete)
Unterbrechung. Dieses Signal wird z.B.von der bash an den aktuell im Vordergrund
laufenden Prozess gesendet, wenn der Benutzer die Tastenkombination
<CTR> <C> drückt und soll meistens die Ausführung
unterbrechen. (Beliebt als Notaustieg aus interaktiven Programmen).
Dieses Verhalten kann der Programmierer aber beeinflussen:
Ein Prozess kann für jedes Signal einen "Signalhandler"
definieren. Das ist eine Funktion die ausgeführt wird wenn der Prozess das
entsprechende Signal erhält.
Die Namen aller Signale des Betriebssystems sind als Keys in einem vordefinierten
Hash namens "%SIG" gespeichert, die entsprechenden Values sind per Voreinstellung leer
("undef").
Man kann sich alle Signale mit dem
PERL-Einzeiler:
foreach ( keys %SIG){ print "\n $_ : $SIG{$_}"; }
ausgeben lassen. Will man jetzt, dass sich das Verhalten eines Programmes ändert, wenn es ein bestimmtes Signal erhält, dann muss man eine Funktionsreferenz unter dem entsprechenden Key in diesem Hash ablegen.
Im folgenden Beispiel (signals.pl) gibt das Hauptprogramm
je nach dem Wert des Flags "$flag" eine Folge von
Minuszeichen oder grossen 'X'-en nach stdout aus.
Die Funktion "onsigint" wird als Handler für das Interrupt-Signal
"INT" registriert:
$SIG{'INT'}=&onsigint;
.
Dadurch wird bewirkt, dass das Programm bei einem
<CTR> <C> Signal von der Konsole die
Konsolenausgabe ändert.
#!/usr/bin/perl -w
#
#
sub onsigint(){
if($flag) {$flag=undef;}
else {$flag=1;}
}
$SIG{'INT'}=\&onsigint;
while (1){
if($flag) {print ("X");}
else {print ("-");}
}
Signalhandler sollten niemals Parameter oder Rückgabewerte besitzen!
Wird eine Funktion mit einer vorher definierten Variable als Parameter aufgerufen,
so wird diese Variable zunächst in das Array "@_" hineinkopiert,
und erst dann von der Funktion bearbeitet. Die Funktion selber arbeitet also nicht
mit der ursprünglichen Variablen, sondern mit einer eigens angelegten Kopie,
oder anders ausgedrückt:
Die Parameterübergabe an Unterfunktionen erfolgt in PERL also per Voreinstellung nach dem "CALL BY VALUE" Prinzip.
Während sich das Call-By-Value Prinzip bei anderen Programmiersprachen vor allem darin äußert, dass Änderungen an einer als Parameter übergebenen Variablen, die in einer Unterfunktion erfolgen, nach der Ausführung der Unterfunktion nicht mehr sichtbar sind, ist bei PERL etwas Vorsicht angebracht. Der folgende Codeschnipsel erzeugt als Ausgabe eine 2, und nicht etwa eine 1, wie man nach obigen Aussführungen eigentlich vielleicht erwarten könnte.
sub increment {
$_[0]=$_[0]+$_[1];
}
$var1=1;
$var2=1;
increment $var1,$var2;
print $var1 ,"\n" ;
Zwar wird die Parameterliste @_ für den Funktionsaufruf neu erzeugt,
aber diese Liste enthält keine Kopien der Parametervariablen, sondern die Parametervariablen
selbst. Eine "tiefe Kopie" der Parameterliste muss explizit erzeugt werden,
die Mühe lohnt sich aber insoweit, dass die Auswertung der Variable $var1 am Ende des
Codeschnipsels das erwartete Ergebnis "1" liefert:
sub increment {
($arg0,$arg1)=@_; # Erzeugen einer tiefen Kopie durch Zuweisung.
$arg1=$arg1+$arg0;
}
$var1=1;
$var2=1;
increment $var1,$var2;
print $var1 ,"\n" ;
Wie andere Programmiersprachen unterscheidet PERL zwischen lokalen und globalen Variablen. Während erstere nur in Unterfunktion bekannt sind in der sie angelegt werden, sind letztere nach ihrer Initialisierung ohne Einschränkung zugänglich.
Ohne weitere Vorkehrungen sind in PERL sogar alle Variablen global . Selbst Variablen die innerhalb von Unterfunktionen angelegt werden sind auch nach dem Terminieren der Unterfunktion noch aktiv und können im weiteren Programmverlauf manipuliert und abgefragt werden:
sub myfunc {
$myvar="eine lokale Variable ? oder nicht ";
}
myfunc;
print $myvar;
Während dieses Verhalten bei kleineren Scripten durchaus komfortabel sein kann, kann ist dieses Verhalten bei größeren Programmen, in denen der Überblick über die eingesetzten Variablennamen verlorengehen kann, nicht mehr akzeptabel. PERL unterstützt daher die Deklaration von lokalen Variablen die nur innerhalb einer Unterfunktion bekannt sind. Lokale Variablen werden bei der Initialisierung durch das vorangestellte Schlüsselwort my deklariert:
sub myfunc {
my $sapcom="eine lokale Variable ? oder nicht ? ";
}
myfunc;
print $sapcom;
Meistens ist es nicht erwünscht, dass globale Variablen, die als Parameter
an Unterfunktionen übergeben wurden innerhalb der Unterfunktion verändert werden,
so wie dies in den Bespielscripten
im Abschnitt über Call By Value /CallBy Reference
geschehen ist. Um dieses Verhalten zu verhindern, empfiehlt es sich die Parameterliste "@_"
in eine nur lokal bekannte Parameterliste umzukopieren:
sub myfunc {
my @args = @_; #umkopieren in lokale Parameterliste
$args[0]=2;
print "Liste der manipulierten Parameter : \n";
print ( join (" ",@args) );
print " \n";
}
@original=(1,2,3,4,5);
myfunc (@original);
print "Original der Parameterliste : \n";
print ( join (" ",@original) );
print " \n";
Durch dieses Umkopieren wird eine tiefe Kopie der Parameterliste erzeugt, also echtes Call by Value bei der die Parameter innerhalb der Unterfunktion beliebig manipuliert werden können, ohne dass diese Änderungen das Terminieren der Unterfunkion überleben.
Hat man, was meistens der Fall sein sollte, eine Vorstellung davon wieviele und welche Parameter der Unterfunktion übergeben werden, dann kann man so einem einzigen Schritt alle Parameter in ein anonymes Array von lokale Varablen kopieren und diese so gleich mit Namen versehen:
sub myfunc{
my ($Parameter1, $Parameter2, $Parameter3) = @_;
print "\n Parameter1 : $Parameter1 \n";
print "\n Parameter2 : $Parameter2 \n";
print "\n Parameter3 : $Parameter3 \n";
}
myfunc ("p1","p2","p3") ;
Diese Konstruktion wird häfig von Programmierern verwendet die die etwas exotische Ansicht vertreten, dass sich guter Stil und die Benutzung von PERL nicht gegenseitig ausschließen.
Bisher war nur von solchen Unterfunktionen die Rede, die keinen Wert zuruckgegeben. PASCAL/Delphi Anhänger würden von solchen Funktionen als "procedures" sprechen, C,PERL und die meisten anderen prozeduralen Programmiersprachen machen keinen grossen Unterschied zwischen Funktionen mit und Funktionen ohne Rückgabewert. Die Rückgabe von Werten erfolgt einfach durch eine Anweisung der Form:
return <wert> ;
Diese Anweisung unterbricht die weitere Ausführung der Funktion und liefert den in <wert> angegebenen Wert als Ergebnis zurück. Der Typ des zurüzuliefernden Wertes muss nicht von vorneherein feststehen, sondern es ist legitim, dass erst während der Laufzeit über den Typ des zurückzuliefernden Objektes entschieden wird, oder ob überhaupt etwas zurückgeliefert werden soll:
sub myfunc {
my ($par)=@_; # ...hier wird guter Stil demonstriert :))
if($par eq "scalar"){ return "ein Scalar";}
if($par eq "array"){ return ("ein","Array"); }
if($par eq "hash"){ return ("ein"=>"Hashtable"); }
print "...na dann wird halt eben nichts zurueckgeliefert !\n"
}
print ( myfunc("scalar"));
print "\n" ;
print ( join (" /n ",myfunc("array")),"\n");
print "\n" ;
print ( myfunc ("hash"));
print "\n" ;
myfunc "unbekannte Eingabe .... ";
Die Eigenart von PERL-Funktionen standardmäßig eine nicht festgelegte Anzahl von Parametern zu akzeptieren, deren Typ nicht a priori feststehen muss , kann je nach Anwendungsfall als Feature oder Bug angesehen werden.
In vielen Anwendungen kann ein solches Verhalten unerwünscht sein. Es macht sicherlich keinen Sinn, eine Funktion, die zwei Parameter addiert mit mehr oder weniger als zwei Parametern aufzurufen. Seit Version 5.0.0.3 bietet PERL die Möglichkeit optional die Parameter einer Funktion festzulegen. Man kann sich damit eine verbesserte Typsicherheit erkaufen, muss es aber nicht. Der folgende Codeschnipsel definiert eine Addition für zwei skalare Parameter:
sub add($$){ print ($_[0]+$_[1])};
Ruft man diese Funktion mit einer Falschen Anzahl von Argumenten auf, so beendet sich perl mit einer Fehlermeldung:
Not enough arguments for main::myfunc at - line 2, near "()"
für den Aufruf mit zuwenig Argumenten , z.B:
add();
add(1);
bzw:
Too many arguments for main::myfunc at - line 2, near ""p2")"
für den Aufruf mit zuvielen Argumenten , z.B:
add((1,2,3)
Die einfachste Möglichkeit einen Funktionsaufruf mit Prototyp zu definieren ist es, in einer eingeklammerten Liste alle Parameter mit ihrem Typ explizit aufzulisten, wobei der Typ des Parameters wie folgt gekennzeichnet wird:
Skalar
Funktion
Hash
Liste
Also z.B: myfunc(&@){ .... }
für eine Funktion myfunc, die einen Skalar und eine Codereferenz als Parameter bekommt.
sub myfunc2 {
print "myfunc2 wird ausgefuehrt.. \n";
}
sub execute (&@){
my($funcref,$scalref,$listref,$hashref)=@_;
print (" codereferenz->Funktionsaufruf \n");
&$funcref ();
print " den Skalar auswerten \n";
print ("$scalref \n")
print " die Liste auswerten \n";
print ( @$listref, "\n") ;
}
execute( sub { myfunc2 },"ein Skalar" );
Vorsicht ist bei Funktionen angebracht, die mehrere Listen oder Hashes als Parameter erhalten. Der Codeschnipsel:
sub printlist(@%){
foreach(@_){
print $_, "\n";
}
}
printlist((1,2),("k1"=>"v1","k2"=>"v2"));
erzeugt die Ausgabe
1
2
k1
v1
k2
v2
Der als zweiter Parameter übergebene Hash wird durch einen impliziten
Typecast zu einer Liste konvertiert, und beide Listen werden
anenandergehängt und als @_ an die Funktion übergeben.
Ohne zusätzliche Informationen über die Parameter ist es danach nicht
mehr möglich den Listenparameter oder den Hashparameter aus der Parameterliste
zurückzugewinnen. Dieser Sachverhalt wird gerne durch den Merksatz
beschrieben, dass ein @ oder ein % alle folgenden
Parameter in der Parameterliste "auffrist".
sollte dieses Verhalten Probleme erzeugen gibt es --auch mit den bisher erwirtschafteten Mitteln-- die Möglichkeit alle Parameter als Referenzen, (also Skalare) zu übergeben, und anschließend durch Dereferenzierung wieder die "Originalparameter" zu gewinnen:
sub printlist($$){
my($listref,$hashref)=@_;
#liste dereferenzieren und ausgeben:
print "\n\n\n Die Liste : " ;
#Hash dereferenzieren und ausgeben:
print @$listref;
print "\n\n Der Hash : " ;
print %$hashref;
print "\n\n\n";
}
printlist([1,2],{"k1"=>"v1","k2"=>"v2"});
Mit dieser Variante des "Call by Reference" Konzeptes verliert man aber wieder die automatisierte Typsicherheit die man durch die Einführung der Funktionsprototypen gerade gewinnen wollte. In obigem Beispiel wäre es z.B. möglich, als zweiten Parameter eine Referenz auf einen Skalar zu übergeben. Dies würde erst beim Versuch den Skalar zu dereferenzieren zu einem Fehler führen, nicht schon beim Aufruf der Funktion printlist.
PERL unterstützt die Möglichkeit
Referenzen unter Angebe des referenzierten Datentyps als
Funktionsparameter zu übergeben. Ein solcher Parameter
wird in der Parameterliste mit dem Symbol für seinen Datentyp,
und zusätzlich einem vorangestellten Backlsash "\"
gekennzeichnet, also mit:
skalare Referenz
Codereferenz
Hashreferenz
Listenreferenz
Mit dieser Technik erreicht man eine vollständige Rückgewinnung der Originalparameter, bei gleichzeitiger Typsicherheit:
sub printlist(\@\%){
my($a1,$a2)=@_;
print "\ndie Liste: \n";
print (@$a1);
print "\nder Hash: \n";
print (%$a2);
print "\n \n"
}
@arg1=(1,2,3);
%arg2=("k1"=>"v1","k2"=>"v2");
printlist(@arg1,%arg2);
Mit einem Semikolon kann man in einem Funktionsprotoyp obligatorische von optionalen Parametern trennen:
sub myfunc($$;$){
my($a1,$a2,$a3)=@_;
print "\n\n---------------------------------------------";
print "\n 1. obligatorischer Parameter: $a1";
print "\n 2. obligatorischer Parameter: $a2";
if($a3){
print "\n optionaler Parameter: $a3";
}
print "\n"
}
myfunc(1,2);
myfunc (1,2,3);
# myfunc(1,2,3,4);
erzeugt die Ausgabe:
---------------------------------------------
1. obligatorischer Parameter: 1
2. obligatorischer Parameter: 2
---------------------------------------------
1. obligatorischer Parameter: 1
2. obligatorischer Parameter: 2
optionaler Parameter: 3
Während ein Entfernen des Kommentarzeichens vor der letzten Aufrufzeile die bekannte Compilermeldung über zuviele Argumente für die Funktion "myfunc" erzeugt.
Für die Strukturierung von Code durch Auslagerung in separate Dateien und die Definition von Namensräumen gibt es in PERL mehrere sich ergänzende Konzepte:
Häufig, d.h in mehreren Programmen benutzte Subroutinen, sollten aus dem Programmcode ausgegliedert und in eigenen Dateien verwaltet werden. Man kann auf diese Weise den Wartungsaufwand drastisch vermindern, da Änderungen an einer Routine nur noch an einer Stelle anfallen.
Die einfachste Möglichkeit externen Programmcode in eigenen Code einzubinden ist die Verwendung
der Funktion "do". Dadurch wird Programmcode aus einer externen Datei gelesen und
ausgeführt,
genauso als wenn Aufruf von "do" durch den Inhalt der externen Datei ersetzt werden würde.
(Oder, als würde man eval() auf den Dateiinhalt anwenden). Der folgende Code demonstriert dieses Verfahren:
(Datei do.pl)
#!/usr/bin/perl -w
do "include_me.pl";
myfunc("it works ");
(Datei include_me.pl):
sub myfunc($){
print "----------------------------------\n";
print "$_[0] \n";
print "----------------------------------\n";
}
"do" erwartet einen Dateinamen als Argument. Diese Datei wird dann in allen Verzeichnissen
gesucht, die in einem Standard-Array mit Namen @INC aufgelistet sind.
Der Inhalt des Suchpfades kann natürlich auch aus dem laufenden Programm heraus mit
push(@INC,$verzeichnispfad); manipuliert werden. CGI-Programmierer, deren Programme auf gemieteten Servern
laufen, müssen sehr oft zu dieser Maßname greifen, wenn sie aktuelle Bibliotheken
nutzen wollen, die der Provider nicht installiert/installieren kann oder installieren will.
Das Parsen und Einbinden einer Datei mithilfe von do() erfolgt zur Laufzeit bei jedem Aufruf von
do. Dieses Verhalten ist problemlos wenn do lediglich verwendet wird, um ein einziges mal
eine Sammlung von Routinen zu laden. Problematisch wird die Verwendung von "do" wenn sie aus
einer Schleife heraus erfolgt, da dies bei jedem Schleifendurchlauf zu einem erneuten Laden führt.
In diesem Fall ist die Verwendung von requireangebracht.
Die Funktion require bindet genauso wie do externen Code in das aktuelle Programm
ein,im Unterschied zu do erfolgt diese Einbindung aber nur einmal, bei anschließenden
Aufrufen wird auf den bereits eingebundenen und compilierten Code im Speicher
zurückgegriffen. Ebenso wie do durchsucht auch require alle Verzeichnispfade in
@INC.
Die Einbindung von externem Code mittels require erfolgt aber nur dann, wenn der eingebundene Code bei der Ausführung einen wahren Wert zurückliefert. Auf diese Weise kann man durch geeigneteten Code in der einzubindenden Datei noch zur Laufzeit entscheiden, ob der Code tatsächlich eingebunden werden soll oder nicht. Bei einem "falschen" Rückgabewert bricht PERL mit einer Fehlermeldung ab:
include_me2.pl did not return a true value at ./require.pl line 2.
.
Will man sicherstellen, dass die Bibliothek unter allen Umständen geladen wird, dann kann man an
das Ende der Datei eine Zeile mit dem Inhalt 1; anzuhängen. Die Auswertung von 1;
erzeugt unter allen Umständen den erforderlichen wahren Rückgabewert. Der folgende Code benutzt dieses
Verfahren:
(require.pl)
#!/usr/bin/perl -w
require "include_me2.pl";
myfunc("it works ");
(include_me2.pl)
sub myfunc($){
print "----------------------------------\n";
print "$_[0] \n";
print "----------------------------------\n";
}
1;
Die Funktion require wird auch benutzt um die noch zu besprechenden
Perl-Module
zu laden.
Da Module per Konvention in Dateien mit der Dateiendung pm vorgehalten werden
ergänzt require Dateinamen die nicht in Anführungszeichen eingeschlossen sind
automatisch um diese Endung. Die folgenden Aufrufe von require sind also gleichwertig:
require ("module.pm");
require (module);
Im
Kapitel über Module wird noch eine weitere Funktion zum Einbinden von externen Code,
die Funktion use besprochen werden.
Packages sind abgeschlossene Namensräume für Funktionen und Variablennamen. Im Programmtext wird ein ein neuer Abschnitt für einen Namensraum wird mit der Direktive
package <packagename>
eingeleitet.
Innerhalb dieses Namensraumes (das heißt: bis zum nächsten Aufruf von package
mit einem anderen Parameter) definierte Funktionen und Variablen können durch das Voranstellen
des Packagenamens und zweier Doppelpunkte referenziert werden. (Sogenannte Fully Qualified Notation)
Ist kein Packagenamen explizit angegeben, dann gehören die entsprechenden Funktionen zum Standardpaket "main",
Alle eingebauten PERL Funktionen gehören zum Standardpaket CORE.
Solche Funktionen können daher auch mit dem vorangestellten Qualifizierer
"main::" bzw CORE:: referenziert werden:
sub hallo { CORE::print("hallo"); }
main::hallo;
Der Gültigkeitsbereich von Packages ist nicht an bestimmte Dateistrukturen gebunden, auch innerhalb einer einzigen Codedatei können mehrere solcher Namensräume definiert werden:
sub hallo { print("hallo \n"); } hallo;
package package1;
sub hallo { print("hallo (Variante 1)\n "); }
hallo;
package package2;
sub hallo { print("hallo (Variante 2) \n"); }
hallo;
package main;
hallo;
package1::hallo;
package2::hallo;
Einen weitereren Schritt hin zur funktionalen Verkapselung und Wiederverwendung von Code stellt
das Modulkonzept dar. Module sind Packages, die in eigenen Dateien abgelegt werden, und die
zusätzlich über genug tolle Eigenschaften verfügen um den ganzen Rest dieses Tutorials
auszufüllen.
Der Name einer solchen Moduldatei besteht aus dem Namen des Moduls, gefolgt von dem
Suffix "pm" (für PERL-Module).
Module werden durch die Verwendung der Funktion
use unter Angabe des Modulnamens eingebunden:
use < modulname >;
Wie bei require funktioniert das Laden der Bibliothek nur dann, wenn
das Modul einen wahren Wert zurückliefert und die Moduldatei über den
@INC Mechanismus gefunden werden kann. Anders als require
wird use nicht zur Laufzeit sondern schon bei der Kompilierung ausgeführt.
Das Zusammenspiel des Programmes zaehlprog.pl:
#!/usr/local/bin/perl -w
print "2\n";
use modul; # eigentlich "modul.pm"
print "3\n";
mit dem Modul zaehlmod.pm:
print "1\n";
1;
Erzeugt die Ausgabe:
1
2
3
Woran man erkennt, dass die use Anweisung vor der Abarbeitung des
Hauptprogramms ausgeführt wurde. Dies führt aber auch dazu, dass
eine Erweiterung des Suchpfades mit einer Anweisung der Form :
push(@INC,$verzeichnispfad);
ohne weitere Maßnahmen beim Laden des Moduls noch nicht wirksam ist, und das Laden daher fehlschlägt. Zur Lösung dieses Problems kann man den push-Befehl in einen BEGIN { ... }-Block einbetten, der schon zur Kompilierungszeit ausgeführt wird:
BEGIN {
:
:
$pfaderweiterung = 'mydir';
push(@INC,$pfaderweiterung);
:
:
}
Das Runtime Environment unterhält während der Ausführung eines Programmes ein Verzeichnis aller Variablen und Funktionen, die sogenannte "Symboltabelle". Für jedes Paket gibt es eine seperate Symboltabelle. Auf diese Weise realisiert jedes Paket seinen eigenen "Namensraum" (englisch: "Namespace") so dass gleichnamige Funtionen oder Variablen die aus verschiedenen Paketen importiert werden trotzdem nicht verwechselt werden.
Die Symboltabelle eines Paketes wird durch einen Hash realisiert.
Der Name dieses Hashes besteht aus dem Namen des Paketes,
an den noch zwei Doppelpunkte angehängt werden.
Man kann den Inhalt der Symboltabelle daher auch mit den gewöhnlichen, von den Hashes
her bekannten Funktionen ansehen oder manipulieren.
Mit my deklarierte Variablen sind nicht in dieser Symboltabelle enthalten, und
damit auch nicht Bestandteil des globalen Namespaces.
Das folgende Programm (symboltable.pl) legt zuerst eine skalare Variable,
eine Liste und einen Hash an, und zeigt dann die keys und values seiner Symboltabelle an:
#!/usr/bin/perl
#
$myscal="ein skalar";
@mylist=(eine,liste);
%myhash={"ein"=>"Hash"};
#
foreach(keys (%main::))
{
print "\n ",$_," -> ",$main::{$_};
}
Der Output (hier nur ausschnittsweise wiedergegeben) zeigt
wie erwartet die Namen der manuell angelegten oder
per Voreinstellung vorhandenen Variablen als keys der Symboltabelle an:
:
mylist -> *main::mylist
stdin -> *main::stdin
STDIN -> *main::STDIN
:
:
stdout -> *main::stdout
STDOUT -> *main::STDOUT
:
:
myscal -> *main::myscal
ENV -> *main::ENV
myhash -> *main::myhash
:
:
Hier fallen die Sternchen vor den Werten (z.B: *main::myscal) auf. Diese Sternchen symbolisieren, dass es sich bei den Werten um die bereits angekündigten "Typeglobs" handelt. Typeglobs sind einzig und alein dazu da Namen von Objekten in der Symboltabelle zu repräsentieren. Ein solcher Typeglob repräsentiert dabei nur den Namen, nicht den Typ eines Objektes, dh: werden z.B. ein Scalar $var, ein Array @var und ein Hash %var angelegt, dann werden alle diese Objekte durch einen gemeinsamen Typeglob *var reprensentiert. (Das Sternchen, das den Typ "Typeglob" auszeichnet, symbolisiert gewissermassen als Wildcard dass der Typglob verschiedene Typen repräsentieren kann).
Es gibt eher wenige Anwendungen für Typeglobs. Sinnvolle Anwendung umfassen das Anlegen von Aliassen (verschiedenen Namen für dasselbe Objekt) und den Zugriff auf Dateihandles über ihren Namen. Dergleichen sprengt aber den Umfang einer Einführung in die Sprache, es sei daher auf das Camelbook verwiesen, in dem diese Techniken detaillierter beschrieben werden.
Nicht alle Pakete werden aus dem "main" Paket heraus geladen. Die meisten Pakete binden ihrerseits wieder dritte Pakete ein. Wir sprechen in so einem Fall von "verschachtelten" Paketen.
Um die Symbole in verschachtelten Paketen zu erreichen wird in der Symboltabelle des ladenden Paketes eine Refrenz auf das zu ladende Paket abgelegt. Der Key zu dieser Referenz besteht aus dem Namen des zu ladenden Paketes, an den noch zwei Doppelpunkte angehängt werden, der Wert des Eintrages aus einem Typeglob, der als Namen den Wert des ladeneden Paketes,
Ein Typeglob für das Standartpaket DynaLoader das aus "main" heraus geladen wird bekommt also als Key die Zeichenkette "DynaLoader::" und als Value den Typeglob *main::DynaLoader:: .
eine Besonderheit stellt das Paket "main" da. Da Objekte dieses Pakets ohne ein vorangestelltes "main::" angesprochen werden verfügt der Typeglob "*main::" über einen weiteren Eintrag mit einer leeren Zeichenkette als key. Damit fungiert jede zeichenkette <String> wie ein Alias für das Symbol main::<String>, und damit auch als Alias für main::main::<String>, main::main::main::<String>, .... usw.