Perl 6

Die Zukunft des Programmierens

Eigene Funktionen schreiben

Wenn Sie diesem Tutorial bis hierhin gefolgt sind, sind Ihnen immer wieder "Dinge" begegnet, die als "Funktionen" bezeichnet wurden, zum Beispiel say, push und andere.

Funktionen sind immer dann gut, wenn man ein Stück Quellcode an verschiedenen Stellen im Programm braucht.

Anstatt dieses Stück Quellcode überall dorthin zu kopieren, wo man es braucht, schreibt man es an einer Stelle in eine sogenannte Funktion, und ruft diese Funktion dann auf, wenn man den Code braucht.

Hier zum Beispiel soll an mehreren Stellen im Programm eine positive Zahl von der Tastatur gelesen werden, und das so lange versucht werden, bis der Benutzer endlich nachgegeben hat:

my $a;
my $b;

$a = $*IN.get;
while $a <= 0 {
	$a = $*IN.get;
}

$b = $*IN.get;
while $b <= 0 {
	$b = $*IN.get;
}

say "Zwei positive Zahlen: $a und $b";

Hier sind vier Zeilen in dem Programm, die sich wiederholen, und nur durch den Namen einer Variable unterscheiden.

Und so sieht es aus, wenn man das ganze in eine Funktion packt:

sub positive_zahl {
	my $zahl = $*IN.get;

	while $zahl <= 0 {
		$zahl = $*IN.get;
	}
	return $zahl;
}


my $a = positive_zahl;
my $b = positive_zahl;

say "Zwei positive Zahlen: $a und $b";

Dieses Beispiel macht genau das gleiche wie das vorherige, nur dass es intern anders aufgebaut ist.

Am Anfang wird in der Zeile

sub positive_zahl {

eine Funktion oder Subroutine, auf Englisch kurz sub definiert, mit dem Namen positive_zahl.

Für Funktionsnamen gelten die gleichen Regeln wie für Variablennamen, nur dass sie offensichtlich nicht mit einem Sonderzeichen anfangen: Sie müssen mit einem Buchstaben oder Unterstrich anfangen, und können aus Buchstaben, Unterstrichen und Ziffern bestehen.

Nach dem Funktionsnamen kommt einen öffnende geschweifte Klammer {, und danach kommt der eigentliche Inhalt der Funktion, der so lange geht, bis die geschweifte Klammer von } geschlossen wird.

In der Funktion taucht die Anweisung return auf, hinter der ein Ausdruck steht - hier die Variable $zahl.

Das sorgt dafür, dass die Funktion beendet wird, sobald die Stelle erreicht wird, an der return steht, und der Ausdruck dahinter wird an den Aufrufer zurückgegeben.

Wenn z.B. ein return 4; in der Funktion steht, und die Funktion mit $a = positive_zahl; aufgerufen wird, hat danach die Variable $a den Wert 4.

Gültigkeitsbereiche für Variablen

Versuchen Sie, folgendes Programm auszuführen:

sub meine_berechnung ($x){
	my $y = 2 * $x;
	return $y * $y;
}

say $y;	    # FEHLER!

Die letzte Zeile wird einen Fehler ausgeben, der sagt, dass die Variable $y nicht deklariert sei.

Tatsächlich wurde sie zwar definiert, aber innerhalb einer Funktion, und in diesem Beispiel wird versucht, auf sie außerhalb der Funktion zuzugreifen. Dort ist sie aber nicht bekannt.

Man sagt, dass die Funktion einen eigenen Gültigkeitsbereich für Variablen erzeugt, auf Englisch "Scope" genannt.

In der Funktion kann man auf Variablen im äußeren Scope zugreifen, wenn sie vorher deklariert wurden:

my $x;

sub say_x {
	say $x;
}

sub change_x {
	$x = 0;
}

$x = 3;
say_x;		# Ausgabe: 3
change_x;	# $x wird jetzt auf 0 gesetzt
say_x;		# Ausgabe: 0

Wenn man innerhalb einer Funktion eine Variable mit my deklariert, die außerhalb dieser Funktion schon vorhanden ist, gilt innerhalb dieser Funktion nur die neu deklarierte "lexikalische" Variable, außerhalb der Funktion aber wieder die alte:

my $x = 3;

say $x;		# Ausgabe: 3

sub irgendeine_funktion {
	my $x;
	$x = 4;
	say $x;	# Ausgabe: 4
}

irgendeine_funktion();

say $x;		# Ausgabe: 3

Diese Regeln gelten nicht nur für Funktionen, sondern für alle Blöcke, d.h. für (fast) alles, was in geschweiften Klammern steht.

In den meisten Programmiersprachen gelten übrigens andere Regeln für den Gültigkeitsbereich von Variablen.

Motivation

Am Anfang sehen diese Regeln kompliziert und vielleicht auch willkürlich aus, aber es gibt sehr gute Gründe dafür.

Der wichtigste ist, dass es sonst sehr schnell zu unangenehmen Namenskollisionen kommen kann:

sub fakultaet($x){
	my $produkt = 1;
	for 1 .. $i -> $i {
		$produkt = $produkt * $x;
	}
	return $produkt;
}

# irgendwo ganz anders im Code:
# Berechne die 10. Zeile des Pascalschen Dreiecks:
# http://de.wikipedia.org/wiki/Pascalsches_Dreieck

loop(my $i = 0; $i < 10; $i++){
	say fakultaet(10) / (fakultaet($i) * fakultaet(10 - $i));
}

Wenn es kein Scoping, also keine verschiedenen Gültigkeitsbereiche für Variablen gäbe, würde folgendes passieren:

  1. In der Schleife wird $i auf 0 gesetzt.
  2. fakultaet(10) wird aufgerufen, darin wird am Ende der for-Schleife $i auf 10 gesetzt.
  3. Ab jetzt enthält $i den Wert 10, d.h. der Aufruf fakultaet($i) wird nach fakultaet(10) überesetzt. Nach diesem Aufruf steht $i immer noch auf 10.
  4. Der dritte Aufruf fakultaet(10-$i) wird zu fakultät(10-10), also fakultaet(0). In deren Berechnung wird die Liste 1..0 durchlaufen, diese Liste ist aber leer, d.h. die Variable $i ändert ihren Wert nicht.
  5. Nach dieser Zeile hat $i den Wert 10, die Bedingung $i < 10 ist also nicht mehr erfüllt, die Schleife bricht ab.

Ohne Scoping würde die Schleife also nicht wie erwartet die 10 Werte (1, 10, 45, 120, 210, 252, 210, 120, 45, 10,) ausgeben, sondern nur einen einzigen Wert, 1.

Das Problem war, dass in zwei verschiedenen Bereichen des Codes Variablen mit gleichen Namen verwendet wurden.

Programme können leicht mehrere Zehntausend Zeilen lang werden - der Linux Kernel hat sogar 6 Millionen Zeilen Programmcode.

In derartig großen Projekten ist es de facto unmöglich, Namenskollision zu vermeiden, wenn die Programmiersprache keine effektive Möglichkeit gibt, damit umzugehen.