Übersicht
- Funktionsdefinition
- Funktionen mit mehreren Rückgabewerten
- Funktionen als Daten
- Closures
- Iteratorfunktionen für das generische for
Funktionsdefinition
Folgend ein Beispiel für eine Funktion mit Rückgabewert und einem Argument (zur Berechnung der Fakultät):function Fakultaet(x)
if x==1 then return x end
return x * Fakultaet(x-1)
end
print(Fakultaet(3)) --> 6
Funktionen können auch lokal definiert werden:
local function cnt(x)
print(x)
end
Bei Funktionsaufrufen gelten die gleichen Regeln, wie bei der Mehrfachzuweisung. Werden mehr Werte übergeben, als die Funktion Argumente hat, so werden die überzähligen ignoriert. Hat die Funktion dagegen mehr Argumente als Werte angegeben werden, so wird nil für die fehlenden Werte angenommen.
Eine nützliche Eigenschaft von Funktionen in Lua ist, dass Tail-Calls optimiert werden. Unter Tail-Calls fallen z.B. Endrekursionen. Tail-Calls sind jedoch viel allgemeiner. Sobald die letzte Aktion einer Funktion ein Funktionsaufruf ist (egal von welcher Funktion) liegt ein solcher Tail-Call vor. Diese werden so optimiert, dass die aufgerufene Funktion die Rücksprungadresse der aufrufenden Funktion erhält und so ein unnötiger Rücksprung zu dieser Funktion vermieden wird. Der Vorteil liegt neben der Geschwindigkeit in der Tatsache, dass man mit Tail-Calls keinen Stack-Overflow erzeugen kann.
Funktionen mit mehreren Rückgabewerten
Funktionen können auch mehere Rückgabewerte haben. Dabei gelten wieder die selben Regeln wie bei einer Mehrfachzuweisung. Dies kann z.B. dazu genutzt werden, um eine Funktion zusätzlich zu einem Wert auch noch Informationen über eventuelle Fehler zurückgeben zu lassen, die bei Bedarf genutzt werden.function myfkt()
return 13, "Fehlermeldung"
end
a, b = myfkt()
print(a, b) --> 13 Fehlermeldung
c = myfkt()
print(c) --> 13
Funktionen als Daten
In Lua sind Funktionen "Werte erster Klasse", was bedeutet, dass sie in Variablen gespeichert werden können. Sie werden wie alle anderen Werte behandelt. Man kann sie also in Tabellen speichern oder als Parameter an Funktionen übergeben. Die oben gezeigte Variante eine Funktion zu definieren ist nämlich nur eine syntaktische Möglichkeit dies zu tun. Eine andere Variante macht stärker deutlich, dass Funktionen Werte sind, wie Zahlen oder Strings.Fakultaet = function(x)
if x==1 then return x end
return x * Fakultaet(x-1)
end
Closures
Funktionen haben Zugriff auf Variablen, die sich in der Umgebung der Funktionsdefinition befinden. Dies und die Tatsache, dass Funktionen Werte erster Klasse sind, geben Lua einen mächtigen Mechanismus Namens "Closures" an die Hand. Closures sind eigentlich nicht viel was anderes als "normale" Funktionen. Sie haben jedoch die zusätzliche Eigenschaft, dass sie intern einen Zustand haben und darin Werte speichern können. Was dies genau bedeutet zeigt folgendes Beispiel:function power(ex)
return function(x)
return x^ex
end
end
square = power(2) -- Closure erzeugen
cubic = power(3) -- Closure erzeugen
print(square(5)) --> 25
print(cubic(3)) --> 27
print(square(4)) --> 16
Hier gibt der Aufruf der Funktion power Closures zurück. Es sind Funktionen, die intern einen Exponenten gespeichert haben. Bei einem Aufruf einer solchen
Funktion wird die Potenz des Argumentes berechnet.Will man beispielsweise eine Funktion erzeugen, die das Quadrat ihres Argumentes berechnent, ruft man power mit 2 (der Exponent) auf. power gibt dann eine solche Funktion zurück. Das Ergebnis wird hier in der Variablen square gespeichert. Das Besondere ist, dass square sich den Wert des Parameters ex (hier 2) gemerkt hat, als es erzeugt wurde. Bei einem Aufruf kann square nun immer auf diesen Wert zugreifen und könnte ihn sogar ändern (was in diesem Beispiel allerding nicht genutzt wird).
Der Grund für dieses Verhalten ist, dass Funktionen auf die Variablen in der Umgebung in der sie definiert wurden zugreifen können. Im Beispiel bedeutet das, dass die anonyme Funktionsdefinition in power auf alle lokalen Variablen in power zugreifen kann. Das ist hier nur der Parameter ex, es könnten aber auch alle anderen lokalen Variablen in Power sein, wenn denn welche definiert worden wären. Von den lokalen Variablen wird quasie eine Kopie für jede Closure angelegt, sodass man auch eine Closure für Kubikzahlen erzeugen kann und die für Quadratzahlen weiterhin funktioniert. Diese Variablen auf die Closures Zugriff haben werden auch als nicht lokale Variablen bezeichnet. Es sind also werder lokale Variablen noch globale Variablen.
Iteratorfunktionen für das generische for
Closures werden unteranderem als Iteratorfunktionen für das generische for genutzt. Im Folgenden ein Beispiel für eine eigene Iteratorfunktion, die der ipairs Funktion ähnlich ist.
function iterator(a)
local i = 0
return function()
i = i + 1
return a[i]
end
end
l = {"Frühling", "Sommer", "Herbst", "Winter"}
for wert in iterator(l) do
print (wert)
end
--> Frühling
--> Sommer
--> Herbst
--> Winter
iterator ist hier die Iteratorfunktion. Ihr Aufruf erzeugt eine Closure, die sich die generische for Schleife merkt. In jedem Schleifendurchlauf wird die Closure aufgerufen und deren Ergebnis ist über wert zugreifbar. Ist das Ergebnis der Closure nil, so bricht die Schleife ab.