Die Community zu .NET und Classic VB.
Menü

Lua lernen - Teil 1

 von 

Einführung 

Versetzen Sie sich einmal in die folgende Situation: Sie haben irgendeine Art von Programm geschrieben (sei es eine Anwendung, eine Bibliothek oder gar ein riesiges Spiel) und wollen nun anderen Entwicklern die Gelegenheit geben, dieses Programm zu erweitern – um eine bestimmte Funktionalität, neue Strategien für die KI, neue Level, egal was. In der Regel werden Sie dafür irgendeine Art von Scriptsprache benötigen und schon stellt sich das Problem: Welche bloß?

Ein anderes Szenario: Sie wollen eine neue Anwendung entwerfen, die auf unterschiedlichen Plattformen lauffähig sein soll, haben jedoch nicht die Zeit, sich in C++ oder andere verbreitete Sprachen einzuarbeiten. Was Sie nun brauchen, ist eine Sprache, die sich schnell erlernen lässt, auf vielen Plattformen einsetzbar und möglichst mächtig ist. Die Sprache darf dabei auch noch performant arbeiten? Gerne. Werfen sie einen Blick in Lua.

Die Vorteile von Lua liegen auf der Hand:

  • Lua ist extrem leichtgewichtig – der komplette Interpreter ist nicht einmal 200kB groß.
  • Lua ist mächtig – Die meisten Probleme sind sehr elegant lösbar.
  • Lua ist schnell – die Performance kommt oft sogar an die von C++ heran.
  • Lua ist erweiterbar – es existieren zahllose Erweiterungen, die leicht einbindbar sind.
  • Lua ist plattformunabhängig – es ist auf jeder Plattform ausführbar, für die ein ANSI-C-Compiler existiert.

Um Sie nun in die Welt von Lua eintauchen lassen zu können, stellen wir auf ActiveVB diese Serie von Tutorials zur Verfügung. In diesem ersten Artikel sollen deswegen die Grundlagen bezüglich Syntax und einiger Konzepte vermittelt werden, um einen ersten Start in die Feinheiten der Sprache zu schaffen. In den Folgeartikeln wird es dann darum gehen, wie einige fortgeschrittene Techniken sinnvoll eingesetzt werden können. Zudem wird die Interaktion von Lua mit diversen Bibliotheken thematisiert, die Funktionalitäten wie 2D- und 3D-Grafiken, Netzwerkanbindung und ansprechende graphische Oberflächen zur Verfügung stellen.

Dieses Tutorial richtet sich dabei vornehmlich an Personen, die bereits mit der Programmierung in einer anderen Sprache vertraut sind und nun Lua lernen wollen. Für Programmieranfänger ist es nicht unbedingt geeignet. Gerade für Personen, die aus der Visual-Basic-Classic-Ecke kommen, wird sich die Syntax schnell vertraut anfühlen, aber auch C++/Java-Entwickler und eigentlich auch jeder andere kann sich schnell in Lua hineinversetzen.

Installation  

Die einfachste Möglichkeit, Lua zu installieren, ist Lua for Windows, welches neben dem eigentlichen Interpreter auch gleich noch einen Texteditor (SciTE) mit Syntaxhighlighting, Autovervollständigung und einigen weiteren hilfreichen Features enthält. Außerdem werden etliche Bibliotheken mitinstalliert, die in den folgenden Tutorials eine Rolle spielen werden. Die Installation an sich sollte problemlos verlaufen. Am Ende erhalten Sie die Möglichkeit, eine kurze Einleitung in Lua zu starten, wenn Sie dies wünschen. Der einzige Nachteil von Lua for Windows ist, dass zum aktuellen Stand noch keine Binaries von Lua 5.2 enthalten sind, stattdessen werden die Binaries von Lua 5.1.4 mitgeliefert. Dies wird allerdings im Verlauf dieses Tutorials keinen Unterschied machen.

Alternativ können natürlich auch die aktuellen Binaries bei SourceForge heruntergeladen werden. Diese lassen sich in der Regel auch problemlos in eine bestehende Installation von Lua for Windows integrieren.

Die ersten Schritte  

Sicherlich kein Tutorial zu einer Programmiersprache kommt ohne das berühmte "Hello World"-Programm aus, weswegen eben dieses hier nun kurz vorgestellt werden soll. Starten Sie zu diesem Zwecke also nun SciTE und tippen Sie den folgenden Code in den Editor ab:

print("Hallo, Welt!")

Listing 1: Hallo Welt in Lua

Als nächstes speichern Sie die Datei unter einem beliebigen Namen ab (beispielsweise hello.lua). Sie haben nun die Möglichkeit, durch Druck auf F5 das so erstellte Programm auszuführen:


Abbildung 1: Ausführung des Hallo Welt-Programms

Herzlichen Glückwunsch, Sie haben soeben Ihr erstes Lua-Programm geschrieben!

Kommentare  

Kommentierung ist in jeder Programmiersprache wichtig. Lua stellt aus diesem Grunde sowohl einzeilige wie auch mehrzeilige Kommentare zur Verfügung:

-- Dies hier ist ein einzeiliger Kommentar.
--[[ 
  Dies ist ein mehrzeiliger
  Kommentar.
  ]]

Listing 2: Kommentare

Bei letzterer Variante ist dabei zu beachten, dass der Kommentar durch das erste Auftreten von "]]" beendet wird. Wird innerhalb des Kommentars allerdings ein "]]" benötigt, so lassen sich beliebig viele Gleichheitszeichen einfügen:

--[==[ 
  Dies ist ein mehrzeiliger
  Kommentar. Er wird nicht durch ]] beendet.
  ]==]

Listing 3: Alternative Kommentare

Variablen  

Variablen sind eines der wichtigsten Elemente in jeder Programmiersprache, doch in Lua nehmen sie einen ganz besonderen Stellenwert ein: In Lua ist ALLES ein Wert und kann somit in einer Variablen gespeichert werden. Das heißt, dass uns hier nicht nur die üblichen Datentypen wie Zahlen, boolesche Werte und Strings erwarten, sondern insbesondere auch Funktionen.

Variablen sind in Lua grundsätzlich global, es sei denn, sie werden mit dem Schlüsselwort local deklariert. In diesem Falle sind sie nur in dem Block verfügbar, in dem sie deklariert worden sind. Lua typisiert grundsätzlich stark und dynamisch, was bedeutet, dass eine Variable grundsätzlich jede Art von Wert aufnehmen kann (so kann eine Variable zunächst eine Zahl, direkt danach aber ebenso einen String aufnehmen, ohne dass es zu einem Fehler kommt). Das bedeutet für den Programmierer natürlich einige Freiheiten, erschwert unter Umständen aber die Fehlersuche.

Lua unterscheidet zwischen den folgenden Datentypen:

  • string
  • number
  • table
  • function
  • userdata
  • boolean
  • thread
  • nil

Dabei ist besonders zu beachten, dass nicht die Variable selbst einen Datentyp hat, sondern lediglich der enthaltene Wert. Neben den bekannten Datentypen (string, boolean und number, welches einem Double gleichkommt) handelt es sich hier noch um einen Felddaten-Typ (eine Art Array), der table heißt, einen Typen, der Funktionen aufnimmt (function) sowie die besonderen Datentypen userdata und thread, mit denen Sie auf absehbare Zeit allerdings nicht in Kontakt kommen werden. Der Datentyp nil ist lediglich dazu da, einen leeren Wert zu beschreiben.

Welcher Datentyp zurzeit konkret vorliegt, lässt sich mithilfe der Funktion type() ermitteln:

local Zahl = 5
local String = "Hallo, Welt!"
local Boolean = true
local nichts = nil

print(type(Zahl))
print(type(String))
print(type(Boolean))
print(type(nichts))

Listing 4: Typen von Werten in Variablen

type() gibt den Namen des Datentypen als string zurück:


Abbildung 2: type() Funktion

Hierbei ist zu beachten, dass es sich bei nil sowohl um einen Datentypen als auch um einen Wert handelt: nil stellt das Fehlen von Daten dar. Bei Umwandlung in einen booleschen Kontext ist nil der einzige Wert, der ebenfalls als false gewertet wird; selbst die Zahl 0 und ein leerer String werden zu true ausgewertet.

Lua arbeitet immer case-sensitive, das heißt, die Bezeichner "String" und "string" beschreiben unterschiedliche Variablen. Schlüsselwörter werden grundsätzlich klein geschrieben.

Funktionen

Da auch Funktionen nichts anderes sind als Werte, lassen sich diese auf ähnliche Art und Weise anlegen:

local doSomething = function(param1, param2)
  print("Etwas erledigen...", param1, param2)
end

doSomething(1, 2)

Listing 5: Funktionen als Werte

Lua bietet dafür allerdings eine weitere Schreibweise an, die ein wenig leichter zu lesen und schneller zu erfassen ist (Syntax-Zucker):

local function doSomething(param1, param2)
  print("Etwas erledigen...", param1, param2)
end

doSomething(1, 2)

Listing 6: Alternative Schreibweise für Funktionen als Werte

Weitere Informationen zum Umgang mit Funktionen finden sich im entsprechenden Kapitel.

tables

Der aber wohl wichtigste Datentyp in Lua ist die table. Diese erfüllt den Zweck eines Arrays und unterstützt dabei Indizes jedes beliebigen Datentypen mit Ausnahme von nil. Richtig gelesen: Neben Zahlen kann auch mit Strings, booleschen Werten und sogar mit Funktionen indiziert werden. Auch die Werte können dabei natürlich jeden beliebigen Datentyp haben. Zunächst werden wir uns aber darauf beschränken, mit Zahlen und Strings zu indizieren.

Der einfachste Weg, eine table anzulegen, ist, einfach alle Werte aufzuzählen, die sie enthalten soll:

local Tabelle = {"1. Wert", "2. Wert", "3. Wert"}
print(Tabelle[2])

Listing 7: Numerisch indizierte table

Dabei erhält das erste Element den Index 1, das darauffolgende den Index 2 und so weiter. Eine table in Lua mit dem Index 0 beginnen zu lassen, ist nicht trivial und bietet einige Nachteile, weswegen man sich hier angewöhnen sollte, Indizes bei 1 beginnen zu lassen.

Um Strings als Index zu verwenden, gibt es zwei Syntaxen:

local Tabelle = {abc = 1, def = 2, ["1. Wert"] = 3, ["2. Wert"] = 4}

Listing 8: Indizierung mit Strings

In der Regel reicht es aus, den zu verwendenden Index ohne Anführungszeichen zu schreiben. Soll der Index allerdings aus nicht-alphanumerischen Werten bestehen, so wird die zweite Schreibweise benötigt: Der Index wird dann in eckige Klammern und Anführungszeichen eingefasst.

Kontrollstrukturen  

if-Verzweigung

Wie jede andere Programmiersprache auch, bietet Lua eine Reihe an Kontrollstrukturen an, um den Lauf eines Programmes zu verzweigen. Insbesondere ist dies natürlich das if-Konstrukt, welches eine sehr ähnliche Syntax aufweist wie sein Pendant in VB6:

local variable = 2 * 3

if variable == 6 then
  print("Rechnung stimmt.")
elseif variable < 6 then
  print("Ergebnis zu klein.")
else
  print("Ergebnis zu groß.")
end

Listing 9: if-Block

Zu beachten ist hier, dass als Vergleichsoperator das doppelte Gleichheitszeichen == herhalten muss. Das einfache Gleichheitszeichen führt in diesem Falle jedoch nicht zu einem Fehlverhalten, wie es etwa in C++ der Fall wäre. Stattdessen wird ein Syntaxfehler bemängelt. Der Operator für Ungleichheit ist ~=.

Eine einzeilige Variante des if, wie sie in VB6 existiert, gibt es in Lua nicht wirklich. Zwar lässt sich eine Bedingung in einer Zeile formulieren, das abschließende end wird aber dennoch benötigt:

local variable = 2 * 3
if variable == 6 then print("Rechnung stimmt.") end

Listing 10: Einzeilige if-Verzweigung

while-Schleifen

Auch die bekannte while-Schleife steht in Lua zur Verfügung. Diese sollte aus anderen Sprachen wohl hinreichend bekannt sein, sodass nur kurz die Syntax demonstriert werden soll:

local zaehler = 0
while zaehler < 10 do
  zaehler = zaehler + 1
  print(zaehler)
end

Listing 11: while-Schleife

repeat-Schleifen

Ebenso wie bei der while-Schleife handelt es sich hierbei um eine Schleife, die so lange ausgeführt wird, wie (oder besser: bis) eine bestimmte Bedingung wahr ist. Der Hauptunterschied besteht dabei darin, dass die repeat-Schleife fußgesteuert ist, die Bedingung wird also erst nach dem ersten Durchlauf der Schleife überprüft. Dies erlaubt etwa die Eingabe und Überprüfung eines Wertes, ohne doppelten Codeaufwand zu haben:

local name
repeat
  print("Bitte geben Sie Ihren Namen ein:")
  name = io.read()
until name ~= ""

Listing 12: repeat-Schleife


Abbildung 3: repeat-Schleife

for-Schleifen

Als letzten Vertreter der Schleifen werden wir uns nun mit der for-Schleife befassen, welche in zwei "Geschmacksrichtungen"“ vertreten ist: Als numerische Schleife, wie sie auch in allen anderen Sprachen vorhanden ist, sowie als generische Schleife, die über beliebig indizierte tables iteriert (in etwa wie die For Each-Schleife in VB6). Hier nun zunächst die numerische Variante:

for i = 1, 20, 2 do
  print(i)
end

Listing 13: Numerische for-Schleife

Hierbei geben die Parameter im Kopf der Schleife zunächst die Untergrenze, dann die Obergrenze und als drittes eine optionale Schrittweite an. Diese Schleife würde also – beginnend mit 1 – alle ungeraden Zahlen ausgeben, die kleiner als 20 sind. Die Zählervariable ist dabei übrigens automatisch lokal nur innerhalb der Schleife verfügbar, ihr Scope endet also mit Beenden der Schleife.

Die generische Version der for-Schleife ist ein wenig komplexer; sie ruft eine sogenannte Iterator-Funktion auf, bis dass diese nil zurückgibt. Die Syntax lautet hierbei:

for v1, ..., vn = Nächstes, t, Startwert do
  -- ...
end

Listing 14: Allgemeine generische for-Schleife

Dabei stellen v1 bis vn alle Werte dar, die von der Funktion next zurückgegeben werden. t ist in der Regel eine table, über die iteriert wird. Da hiermit allerdings nichts anderes passiert, als dass dieser Wert an die next-Funktion übergeben wird, kann es sich um beliebige Daten handeln. Startwert zu guter Letzt gibt einen Index an, von dem aus die Funktion next den nächsten Schlüssel generiert.

Um übliche Aktionen, wie etwa die Iteration über eine table, zu vereinfachen, stellt Lua Funktionen bereit, die die drei Werte next, table und Startwert liefern. Die zwei wichtigsten dieser Funktionen sind pairs und ipairs, die jeweils über eine table iterieren.

pairs()

Die Funktion pairs() ermöglicht, über eine table mit beliebigen Indizes zu iterieren. Dabei ist die Reihenfolge, in der die Werte auftauchen, nicht definiert:

t = {abc = 1, def = 2, ghi = 3}
for k, v in pairs(t) do
  print(k, v)
end

Listing 15: for-Schleife mit pairs()


Abbildung 4: pairs()

Hierbei gibt k jeweils den Index an, v den Wert. Dieser Wert lässt sich nicht direkt verändern (v = v + 1), wohl aber indirekt, indem er via t[k] = v + 1 gesetzt wird. Auch ist das Verhalten der Schleife undefiniert, wenn währenddessen neue Werte eingefügt werden. Werte zu löschen stellt hingegen in der Regel kein Problem dar.

Insbesondere numerische Werte werden bei dieser Funktion nicht in der erwarteten Reihenfolge ausgegeben. Aus diesem Grund gibt es zudem die Funktion ipairs().

ipairs()

Liegt ein numerischer Index vor, so liefert die Funktion pairs() einen Iterator, der die numerische Reihenfolge nicht notwendigerweise einhält. ipairs() hingegen lässt den Index grundsätzlich immer hochzählen:

t = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J"}
for i, v in ipairs(t) do
  print(i, v)
end

Listing 16: ipairs()

ipairs() ist allerdings nur beschränkt hilfreich, da sie immer bei Index 1 beginnt (0-basierte Arrays/tables sind somit nur auf Umwegen möglich) und nicht bis zum höchsten Index zählt, sondern lediglich so lange, bis sie auf einen nicht vorhandenen Index stößt. In folgendem Beispiel wäre der Einsatz von ipairs() also nicht zu empfehlen:

t = {[1] = "A", [2] = "B", [26] = "Z"}
for i, v in ipairs(t) do
  print(i, v)
end

Listing 17: Ungeschickte Anwendung von ipairs()


Abbildung 5: Ungeschickte Anwendung von ipairs()

Der Missstand, dass nicht über alle Elemente von 0-basierten tables iteriert wird, lässt sich zwar mit einer numerischen Schleife einfach lösen, dies gilt jedoch nicht für das Problem, dass nicht bis zum höchsten Index iteriert wird. Besonders problematisch ist hierbei, dass auch der Längen-Operator für tables nicht den höchsten Index angibt. Anders, als man nun vermuten möchte, gibt er allerdings auch nicht die Gesamtzahl enthaltener Elemente an, sondern lediglich den ersten Index, auf den ein nil folgt. Dies gilt für alle tables, die numerisch indizierte Werte enthalten.

Sofern es beim Iterieren über eine table mit numerischen Indizes nicht notwendigerweise um die Reihenfolge geht, in der die Elemente ausgegeben werden, bietet sich aus diesen Gründen oft dennoch eine Schleife mit pairs() an:

t = {[1] = "A", [2] = "B", [26] = "Z"}
for i, v in pairs(t) do
  print(i, v)
end

Listing 18: Alternative Iteration mit pairs()

Ansonsten wird im Kapitel Datentyp table noch eine Möglichkeit aufgezeigt, die doch etwas steife Funktion ipairs() flexibler zu gestalten.

Operatoren  

An dieser Stelle seien noch einmal kurz alle in Lua verwendbaren Operatoren kurz aufgelistet und erläutert.

Arithmetik

Operator Bedeutung
+ Addition
- Subtraktion/Unäres Minus
* Multiplikation
/ Division
^ Potenz
% Modulo

Vergleich

Operator Bedeutung
== Gleichheit
~= Ungleichheit
< Kleiner als
<= Kleiner als oder gleich
> Grösser als
>= Grösser als oder gleich

Besondere Operatoren

Operator Bedeutung
= Zuweisung
# Länge
.. Stringverkettung
( ) Aufruf
[ ] Indizierung

Logik

Operator Bedeutung
and Logisches UND
or Logisches ODER
not Logisches NICHT

Die Logik-Operatoren bedürfen hierbei wohl ein wenig Erläuterung, da sie grundsätzlich anders arbeiten, als sie es in anderen Sprachen tun. Der Operator and gibt hierbei den zweiten Operanden zurück, wenn der erste zu true ausgewertet werden kann, ansonsten gibt er den ersten Operanden zurück. Der or-Operator gibt den ersten Wert zurück, der nicht zu false ausgewertet wird.

Wie bereits erwähnt werden alle Werte, die nicht false oder nil sind, als true gewertet. Dies ermöglicht das folgende Sprachkonstrukt:

a = true
print(a and "wahr" or "falsch")

Listing 19: Verzweigungen mit logischen Operatoren

Dabei wird "wahr" ausgegeben, wenn a true ist, ansonsten wird "falsch" ausgegeben. Es entsteht der ternäre Operator, wie er aus anderen Sprachen (C++, Java und ähnliche) bekannt ist. Das VB6-Pendant dazu wäre die Funktion IIf(), mit dem Unterschied, dass nur der Ausdruck ausgewertet wird, der auch zurückgegeben wird.

Ein weiterer Vorteil, der sich aus der Funktionsweise der logischen Operatoren ergibt, ist, Parameter mit einem bestimmten Wert vorzubelegen, wenn keiner übergeben worden ist:

local function foobar(a, b)
  a = a or 10
  b = b or 20

  print(a, b)
end

foobar(1, 2)
foobar(3)
foobar(nil, 4)
foobar()

Listing 20: Default-Argumente mit logischen Operatoren


Abbildung 6: Optionale Parameter

In diesem Falle wird der zweite Operand, also der Standardwert, genau dann zurückgegeben, wenn der erste Operand, hier also der Parameter, nil ist. Das ist der Fall, wenn ein Parameter zwar in der Parameterliste definiert, beim Aufruf aber nicht übergeben wird.

Bitweise Logik

Bitweise Operatoren unterstützt Lua nativ erst seit Version 5.2, davor war man auf externe Bibliotheken (oder selbstentwickelte Funktionen) angewiesen. Seit Lua 5.2 sind diese Funktionen über bit32.b>Operator< verfügbar:

Funktion Bedeutung
bit32.band Bitweises UND
bit32.bnot Bitweises NICHT
bit32.bor Bitweises ODER
bit32.bxor Bitweises Exklusiv-ODER
bit32.lrotate Bitrotation nach links (rechts für negative Werte)
bit32.rrotate Bitrotation nach rechts (links für negative Werte)
bit32.lshift Bitshifting nach links (rechts für negative Werte)
bit32.rshift Bitshifting nach rechts (links für negative Werte)
bit32.arshift Arithmetisches Shifting nach rechts (links für negative Werte, bit32.alshift existiert nicht)

Die bit32-Bibliothek verfügt noch über ein paar weitere Funktionen, auf die hier allerdings nicht eingegangen wird. Bei Interesse liefert das Reference Manual weitere Informationen.

Datentyp table  

Wie bereits erwähnt, stellen tables die wichtigste Datenstruktur in Lua dar. Sie dienen als Arrays, als Dictionaries, Hashmaps, Graphen und sogar als Klassen.

Zunächst einmal lassen sich tables mit dem Table-Konstruktor { } erstellen, den Sie bereits im Kapitel über die unterschiedlichen Datentypen gesehen haben. Dieser bietet die Möglichkeit, sowohl automatisiert numerische Indizes zu vergeben, da jedes Element, dem nicht explizit ein Index zugeordnet wird, den nächsthöheren Index erhält. Zudem können durch Klammerung in eckige Klammern auch beliebige Werte als Index vergeben werden:

local t = {
  [2] = "B",
  [4] = "D",
  [6] = "F"
}

Listing 21: table-Konstruktor

Werden auf diese Art und Weise Strings als Index verwendet, können sowohl die eckigen Klammern als auch die Anführungszeichen entfallen, solange die Indizes keine Leerzeichen oder sonstige Sonderzeichen enthalten:

local t = {
  ["a"] = 1,
  ["b"] = 2,
  c = 3,
  d = 4
}

Listing 22: table-Konstruktor mit Strings

Zudem lässt sich außer nil jeder andere Datentyp als Index gebrauchen. Wenngleich sich über Sinn und Unsinn diskutieren lässt, kann es von Zeit zu Zeit durchaus praktisch sein, etwa eine Funktion als Index zu verwenden (Stichwort Callbacks, zu denen weiterführende Informationen gespeichert werden).

Der Zugriff auf tables erfolgt grundsätzlich über den []-Operator, lediglich für String-Indizes steht als syntaktischer Zucker der Zugriff über den .-Operator zur Verfügung:

local t = {
  ["a"] = 1,
  ["b"] = 2,
  ["c"] = 3,
  ["d"] = 4
}

print(t.a, t.b, t.c, t.d)

Listing 23: Zugriff auf Elemente einer table

Module

Zudem können tables natürlich Werte jedes Datentyps annehmen. Eine besondere Rolle spielen dabei Funktionen, die sich somit zu Modulen zusammenfassen lassen:

myModule = {
  echo = function(expression)
    print("Ausgabe: " .. expression)
  end,

  output = function(expression)
    print("Die Ausgabe war " .. expression .. ".")
  end
}

myModule.echo("Hello, World!")
myModule.output("Hello, World!")

Listing 24: table als Modul

Auch Lua selbst implementiert Module auf diese Art und Weise; so lässt sich beispielsweise das Modul io komplett selbst gestalten, wenn die Notwendigkeit dafür besteht. Zudem können Funktionen beliebig in bestehende Module (oder auch Klassen, beispielsweise die string-Klasse) eingefügt werden:

function string.output(expression)
  print(expression)
end

string.output("abc")

Listing 25: Erweiterung der string-Klasse

Lua bietet hierfür wiederum syntaktischen Zucker an:

local str = "abc"
str:output()

Listing 26: Vereinfachte Schreibweise

Diese Syntax wird besonders im Zusammenhang mit metatables sowie später bei der objektorientierten Programmierung eine besondere Rolle spielen.

Funktionen  

Funktionen sind in Lua erst einmal nichts grundlegend anderes als in anderen Programmiersprachen, weisen allerdings ein paar Unterschiede auf, die durchaus nützlich sein können. Hier ist zunächst zu erwähnen, dass einer Funktion eine beliebige Anzahl an Parametern übernehmen kann. Werden mehr Parameter übergeben, als im Kopf der Funktion benannt sind, so werden die überzähligen Parameter schlicht verworfen:

function doSomething(a, b)
  print(a, b)
end

doSomething(1, 2, 3, 4, 5)

Listing 27: Zu viele Parameter


Abbildung 7: Zu viele Parameter

Werden hingegen weniger Parameter übergeben, als definiert sind, werden alle fehlenden mit nil aufgefüllt:

function doSomething(a, b, c)
  print(tostring(a), tostring(b), tostring(c))
end

doSomething(1)

Listing 28: Zu wenige Parameter


Abbildung 8: Zu wenige Parameter

Dieses Verhalten wurde bereits im Absatz zu den logischen Operatoren erwähnt, da diese sich hervorragend dafür eignen, optionale Parameter mit Standardwerten vorzubelegen.

Ebenfalls interessant ist die Möglichkeit, beliebig viele Parameter entgegen zu nehmen (wie etwa mit einem ParamArray in Visual Basic).

Dafür steht der Platzhalter ... zur Verfügung:

function printMany(...)
  local tmp = {...}

  for k, v in pairs(tmp) do
    print(k .. "ter Wert: " .. tostring(v))
  end
end

printMany("abc", 123, "Hallo, Welt!")

Listing 29: Beliebig lange Parameterliste

Zu beachten ist dabei, dass ... keine table, sondern tatsächlich eine Abfolge von Werten darstellt, weswegen es

  1. zunächst in eine table umgewandelt werden muss, um damit arbeiten zu können und
  2. direkt an andere Funktionen weitergegeben werden kann.

Die Funktion print, die wir benutzen, um Ausgaben auf der Konsole zu erzeugen, nimmt übrigens auf dieselbe Art und Weise eine beliebige Anzahl an Werten entgegen.

Die letzte Eigenschaft von Funktionen, die behandelt werden muss, ist der Rückgabewert. Jede Funktion kann mithilfe der Anweisung return eine beliebige Anzahl an Werten zurückgeben. Viele in Lua eingebaute Funktionen haben etwa die Eigenschaft, bei Fehlschlag erst nil und als zweiten Wert eine Fehlermeldung auszugeben:

local f, err = io.open("foobar.txt", "r")

if not f then
  print(err)
end

Listing 30: Statusmeldung mittels return


Abbildung 9: Funktionsaufruf mit Fehlermeldung

Um selbst mehrere Werte zurückzugeben, reicht es, diese bei der return-Anweisung anzugeben:

function returnMany()
  return 1, 2, 3, "a", "b", "c"
end

r1, r2, r3, r4, r5, r6 = returnMany()

print(r1, r3, r5)

Listing 31: Rückgabe von mehreren Werten


Abbildung 10: Rückgabe von mehreren Werten

Auch hier gilt: Werden weniger Werte entgegengenommen, als zurückgegeben, werden die überzähligen Rückgabewerte verworfen. Werden hingegen mehr entgegengenommen, als zurückgegeben, werden die restlichen mit nil aufgefüllt.

Mit diesem Wissen können wir nun auch die versprochene, erweiterte Version von ipairs verstehen:

function ipairs(t)
  local maxi = 0
  local mini

  for k, v in pairs(t) do
    if not mini or mini > k then mini = k end
    if maxi < k then maxi = k end
  end

  local func = function(t, i)
    if i < maxi then
      repeat
        i = i + 1
      until t[i]

      return i, t[i]
    end

    return nil
  end

  return func, t, mini - 1
end

t = {[0] = "-", [1] = "A", [2] = "B", [26] = "Z"}
for i, v in ipairs(t) do
  print(i, v)
end

Listing 32: Erweiterte ipairs-Funktion

Diese Version hat natürlich den Nachteil, dass sie nicht ganz so effektiv arbeitet wie das Lua-eigene ipairs (allein schon dadurch, dass vor der eigentlichen Iteration einmal über die komplette table iteriert werden muss, um den höchsten Index zu finden), allerdings ist sie wie erwähnt flexibler, was Beginn und Lücken der Indizierung angeht.

Schlusswort  

Ich hoffe, Sie konnten mit dieser Einführung einiges über die Sprache Lua lernen und sind ebenso von ihr begeistert, wie ich es bin. Sollten Sie Fragen bezüglich des Tutorials oder auch zum Umgang mit Lua allgemein haben, sind sie herzlich eingeladen, diese im Stammtisch-Forum zu diskutieren. Ansonsten hoffe ich, dass der nächste Teil des Lua-Tutorials, in dem es um fortgeschrittene Techniken und insbesondere um Objektorientierte Programmierung gehen wird, Ihnen ebenfalls hilfreich sein kann.

Ihre Meinung  

Falls Sie Fragen zu diesem Tutorial haben oder Ihre Erfahrung mit anderen Nutzern austauschen möchten, dann teilen Sie uns diese bitte in einem der unten vorhandenen Themen oder über einen neuen Beitrag mit. Hierzu können sie einfach einen Beitrag in einem zum Thema passenden Forum anlegen, welcher automatisch mit dieser Seite verknüpft wird.