Lua: Funktionen


< Anweisungen
Gesamtübersicht (Start) / Seminarthemen WS 2009/10

Übersicht


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.

< Anweisungen
Zum Seitenanfang