[Inhalt]...[Nicht-strike Auswertung]...["Strictness" in Haskell]
Als Beispiel
sei eine Funktion gegeben, die zwei ganze Zahlen durcheinander teilt, welche als
Parameter übergeben werden. Ist der Divisor (im Beispiel der zweite Parameter)
null, wird die Berechnung der Division innerhalb der Funktion einen Fehler
auslösen. Das Programm wird, falls keine Fehlerbehandlung besteht, sofort
beendet. Die Funktion terminiert nicht, und liefert keinen Wert zurück.
Der Code:
int idiv (int a, int b)
int main (void)
Ausgabe des Programms (gcc unter cygwin32):
Aufgrund der strikten Auswertung wird von der Funktion "idiv" nicht der Ausdruck
Die strikte Auswertung spielt insbesondere bei der Parameterübergabe per Wert ("Call-by-value", wie
oben) und per Referenz ("Call-by-reference") eine Rolle, welche im folgenden noch einmal
vorgestellt werden.
Quelle: "Duden Informatik", 2. Auflage, Dudenverlag
1993
Beispiel für kopierte aktuelle Parameter in Ansi C:
void padress (int i)
int main (void)
Die Ausgabe des Programms
Der Wert der Variablen i hat also eine andere Speicheradresse, als der des
Wertparameters i, was durch das Kopieren der aktuellen Parameter erklärt ist.
Daraus folgt auch, dass keine Seiteneffekte auftreten können, falls
der Wert des Parameters in der Funktion geändert wird. Nach Rückkehr in die Hauptprozedur kann
auf dem ursprünglichen Wert weitergearbeitet werden, wie das zweite Beispiel zeigt.
Beispiel in C: [Quelldatei]
void tausche(int a,int b)
Ausgabe des Programms:
Die Funktion "tausche" operiert lediglich auf den kopierten aktuellen
Parametern. Nach Beenden der Funktion besteht kein Zugriff mehr auf
ihren Speicherbereich. Die in der Hauptprozedur gespeicherten Variablen
x und y behalten den ursprünglichen Wert, somit erfüllt die Funktion
nicht, was ihr Name verspricht...
Quelle: "Duden Informatik", 2. Auflage, Dudenverlag 1993
Beispiel in C [Quelldatei]
void tausche(int *
a,int * b)
Hinweis:
[Inhalt]...[Nicht-strike Auswertung]...["Strictness" in Haskell]
3.1 Erklärung
Die meisten Programmiersprachen, insbesondere
die imperativen und Objekt-orientierten, werten Ausdrücke strikt aus. In diesem
Fall wird ein Ausdruck nie als solcher behandelt, sonder stets zu einem Wert
ausgewertet. Daraus resultiert die Tatsache, dass eine Funktion, die auf einem
nicht terminierenden Ausdruck arbeitet, selbst nicht terminiert.
#include <stdio.h>
{
return a/b;
}
{
fprintf(stdout,"2 / 3 = %d\n", idiv(2,3));
fprintf(stdout,"2 / 0 = %d\n", idiv(2,0));
return 0;
}
2 / 3 = 0
0 [main] fdiv 1628 handle_exceptions: Exception: STATUS_INTEGER_DIVIDE_BY_ZERO
[...]
a/b
zurückgeliefert, sondern sein Wert. Im ersten Fall wird
2/3
zu null ausgewertet, da "drei null mal in zwei passt". Im
zweiten Fall muss 2/0
berechnet werden, was zu einem arithmetischen
Ausnahmefehler führt, weil der Wert nicht definiert ist. Die Auswertung bricht
ab, ebenfalls die Funktion, sowie das gesamte Programm. Ein Rückgabewert ist
nicht definiert. Der "Haskell 98 Report" sieht folgende Notation vor: Der Wert
eines nicht terminierenden Ausdrucks wird mit "_|_" (sprich engl.
"bottom") angegeben.3.2 "Call-by-value"
Definition "Call-by-value" Übergabeart für Parameter einer Prozedur, bei der
beim Prozeduraufruf nur der Wert des aktuellen Parameters übergeben wird, nicht
jedoch der Name oder die Adresse, unter welcher der Ausdruck im Speicher
steht.
Bei der Übergabe per Wert werden innerhalb einer aufgerufenen Funktion private Kopien der übergebenen
Parameter angelegt, auf denen die Funktion arbeitet. Entsprechend der im Funktionskopf angegebenen
formellen Parameter wird bei Aufruf eine Kopie jedes aktuellen Parameters im Stackbereich
der Funktion gespeichert. Verdeutlicht wird dies durch die Ausgabe folgenden Programms.
Es gibt zunächst die Adresse einer Variable der Hauptprozedur aus, welche dann als Parameter an
die Funktion "padress" übergeben wird. Diese gibt erneut die Adresse des aktuellen Parameters aus.
#include <stdio.h>
{
printf("Funktion: &i = %p\n",&i);
}
{
int i;
printf("Hauptprogramm: &i = %p\n",&i);
padress(i);
return 0;
}
Hauptprogramm: &i = 0x240feb0
Funktion: &i = 0x240fe8c
#include <stdio.h>
{
int tmp=a;
a=b;
b=tmp;
}
int main (void)
{
int x=1,y=2;
fprintf(stdout,"Vor tausche(x,y) : x=%d y=%d\n",x,y);
tausche(x,y);
fprintf(stdout,"Nach tausche(x,y): x=%d y=%d\n",x,y);
return 0;
}
Vor tausche(x,y) : x=1 y=2
Nach tausche(x,y): x=1 y=2
3.3 "Call-by-reference"
Definition "Call-by-reference"
Gängige Parameterübergabe in imperativen Programmiersprachen, wobei die
Prozedur(en) unmittelbar mit den aktuellen Parametern arbeiten, und nicht nur
auf Kopien der Werte.
#include <stdio.h>
{
int tmp=*a;
*a=*b;
*b=tmp;
}
int main (void)
{
...
tausche(&x,&y);
...
}
Ausgabe des Programms:Vor tausche(x,y) : x=1 y=2
Nach tausche(x,y): x=2 y=1
Der formale Parameter einer Funktion wird als Zeiger auf den Parametertyp
deklariert. Bei Auswertung des aktuellen Parameters wird die Adresse des
Ergebnisses verwendet, die bei Verwendung des formalen Parameters innerhalb der
Funktion wird jeweils eingesetzt wird. Die Konsequenz ist, dass der aktuelle
Parameter verändert wird, falls durch die Referenz Änderungen vorgenommen
werden. Dieser Seiteneffekt ist bei richtiger Benutzung gewollt, z.B. beim
Aufbau einer Liste.
Formal unterstützt die Sprache C nur Wertparameter. Entsprechend des oben
genannten Prinzips können Referenzparameter aber simuliert werden,
in dem mit Zeigertypen gearbeitet wird.