Programmieren lernen mit Lua: Lektion 04: Funktionen selber schreiben
Übersicht | Vorherige Lektion | Nächste Lektion
ARVE Error: src mismatch
provider: youtube
url: https://www.youtube.com/watch?v=sCoVnAHO8BM
src in org: https://www.youtube-nocookie.com/embed/sCoVnAHO8BM?feature=oembed&width=840&height=1000&discover=1
src in mod: https://www.youtube-nocookie.com/embed/sCoVnAHO8BM?width=840&height=1000&discover=1
src gen org: https://www.youtube-nocookie.com/embed/sCoVnAHO8BM
Wir haben bereits ab der ersten Lektion Funktionen verwendet:
print() type() io.read() tonumber() math.random()
Du kannst Dir eine Funktion wie eine Portion Code vorstellen, die Du zum wiederholten Ausführen zusammenfasst. Genau so gut und richtig ist die Idee, eine Funktion als ein Unterprogramm zu sehen, das auf eine bestimmte Aufgabe spezialisiert ist. Anstatt an jeder Stelle, in der Du ihn brauchst, den kompletten Code hinzuschreiben, kannst Du ihn in eine Funktion verpacken und diese Funktion dann an beliebigen Stellen in Deinem Programm verwenden.
Die Verwendung einer Funktion wird oft Funktionsaufruf genannt. Ein Funktionsaufruf besteht im Normalfall aus dem Namen der Funktion und einem Paar runder Klammern. Zwischen den Klammern stehen oft ein oder mehrere Argumente. Argumente sind Werte, welche genauer beschreiben, was die Funktion machen soll.
> print("guten tag") guten tag
In diesem Fall ist der Name der Funktion print()
. Das Argument ist der String "guten Tag"
.
Manche Funktionen können auch mehrere Argumente verarbeiten, das gilt auch für print()
:
> print("dies", "das", "jenes") dies das jenes
Andere Funktionen benötigen kein Argument, zum Beispiel io.read()
. Die runden Klammern gehören trotzdem immer zum Funktionsaufruf:
eingabe = io.read()
Die runden Klammern unterscheiden den Funktionsaufruf von der Funktion selbst:
> type(print) function > type(print()) nil
io.read()
ist eine Funktion, die einen Wert zurückliefert. Dazu werden wir später noch einiges sagen.
Eine Pizzeria programmieren
Stell Dir vor, Du möchtest ein Textadventure programmieren, in dem es um den Betrieb einer Pizzeria geht. Jedes Mal, wenn ein Kunde eine Pizza bestellt, soll das Programm den Arbeitsablauf der Zubereitung beschreiben. Eine erste Variante dieses Programmteils könnte so aussehen:
print("Teig ausrollen") print("Tomatensoße hinzu") print("geriebenen Käse hinzu") print("backen")
Die Pizza per Funktion zubereiten
Es ist ein leichtes, aus diesem Code eine Funktion zu bauen:
function pizza_backen() print("Teig ausrollen") print("Tomatensoße hinzu") print("geriebenen Käse hinzu") print("backen") end
Das Schlüsselwort function
bedeutet: hier kommt eine Funktionsdefinition. pizza_backen
ist der Name der Funktion, die hier definiert wird. Die leeren runden Klammern zeigen an, dass die neue Funktion keine Argumente hat. Die darauf folgenden, eingerückten Zeilen bis zum end
ist der Code, der ausgeführt werden soll, wenn jemand die Funktion aufruft. Wir sprechen auch hier (genau wie bei der bedingten Ausfühung per if
oder bei Schleifen per while
und for
) von einem Block. Bei Funktionen spricht man auch oft vom Körper der Funktion. Auch hier ist eine Einrückung des Körpers üblich.
Wenn Du das letzte Beispiel ausführst, passiert erst einmal nichts. Du hast ja die Funktion lediglich definiert. Ein Pizzarezept ist noch keine Pizza! Um die Funktion zu verwenden, schreibst Du ganz einfach:
pizza_backen()
In diesem Fall definieren wir die Funktion in der Datei main.lua
, verwenden sie aber im interaktiven Modus auf der Konsole:
Aufgabe 1: Schreibe eine eigene Funktion tee_kochen()
, welche die Zubereitungsschritte einer Tasse Tee in den drei Schritten „Wasser kochen“, „Teebeutel in Tasse hängen“ und „Wasser aufgießen“ nach dem Muster unserer Pizza-Funktion beschreibt. Führe die Funktion drei Mal aus. Wenn Du keinen Tee magst, kannst Du auch gerne ein anderes Rezept oder eine Bauanleitung verwenden!
function tee_kochen() print("Wasser kochen") print("Teebeutel in Tasse hängen") print("Wasser aufgießen") end tee_kochen() tee_kochen() tee_kochen()
Du kannst bereits an dem Pizza Beispiel sehen: Der Code innerhalb der Funktion (hier: pizza_backen
) kann wiederum andere Funktionen (hier: print()
) aufrufen. Selbstverständlich können auch selbst geschriebene Funktionen wiederum selbst geschriebene Funktionen aufrufen. Bei der Entwicklung komplexerer Programme besteht ein Gutteil der Arbeit darin, ein erstmal schwer überschaubares Problem in überschaubare, leicht verständliche Funktionen zu zerlegen.
Aufgabe 2: Schreibe eine Funktion teig_zubereiten()
, welche die Beschreibung der Zubereitung eines Pizza Teiges ausgibt:
Wasser, Mehl, Hefe, Öl und Salz in Schüssel geben Zutaten rühren Teig gehen lassen
Rufe diese Funktion innerhalb der Funktion pizza_backen()
auf, bevor der Teil ausgerollt wird.
function teig_zubereiten() print("Wasser, Mehl, Hefe, Öl und Salz in Schüssel geben") print("Zutaten rühren") print("Den Teig gehen lassen") end function pizza_backen() teig_zubereiten() print("Teig ausrollen") print("Tomatensoße hinzu") print("geriebenen Käse hinzu") print("backen") end
Die Pizzafunktion um ein Argument erweitern
Eine Pizzeria, in der es nur die Standart Margherita Pizza gibt, würde sich wohl nicht lange halten. Es sollte zum Beispiel auch Pizzen mit Zwiebeln oder Pilzen geben. Eine Möglichkeit wäre, für jede Sorte eine eigene Funktion zu schreiben:
function pizza_mit_pilzen_backen() print("Teig ausrollen") print("Tomatensoße hinzu") print("Pilze hinzu") print("geriebenen Käse hinzu") print("backen") end function pizza_mit_zwiebeln_backen() print("Teig ausrollen") print("Tomatensoße hinzu") print("Zwiebeln hinzu") print("geriebenen Käse hinzu") print("backen") end
Die beiden Funktionen unterscheiden sich nur in einer einzigen Zeile. In der Softwareentwicklung gibt es eine Faustregel: Wenn ihr längere identische Codeabschnitte mehrfach in eurem Code stehen habt, dann solltet ihr überlegen, wie ihr den wiederholten Code zusammenfassen könnt. Im gegebenen Fall ist das sehr einfach: Wir spendieren der Funktion ein Argument extrabelag
:
function pizza_backen(extrabelag) print("Teig ausrollen") print("Tomatensoße hinzu") print(extrabelag .. " hinzu") print("geriebenen Käse hinzu") print("backen") end
Das geht ganz einfach, wie das Beispiel zeigt: im Upgrade unserer Pizza-Funktion steht jetzt ein Bezeichner, eben extrabelag
. Das zeigt an, dass diese Funktion ein Argument verarbeiten kann. Wenn die Funktion zum Beispiel mit dem Argument "Pilze"
aufgerufen wird – pizza_backen("Pilze")
– dann wird überall dort, wo im Körper der Funktion der Bezeichner extrabelag
auftaucht durch den Wert "Pilze"
ersetzt. Das Argument bestimmt den veränderlichen Teil der Funktion.
Mit dieser Funktion könnt ihr nunmehr Pizzen mit beliebigen Extrabelägen backen:
pizza_backen("Spiegelei") pizza_backen("Schuhsohle") pizza_backen("Vanilleeis")
Es gibt an der neuen Funktion allerdings einen Haken: Wenn ihr sie ohne Argument aufruft, quittiert der Lua-Interpreter das mit einer Fehlermeldung:
$ pizza_backen() attempt to concatenate local 'extrabelag' (a nil value)
Das Bedeutet auf deutsch: Versuch, den Wert extrabelag
anzuhängen ist gescheitert, weil dieser Wert nil
ist. Dieser Fehler passiert in der Zeile
print(extrabelag .. " hinzu")
Hintergrund dieses Fehlers ist: Wenn bei dem Aufruf unserer Funktion kein Argument verwendet wird, ist extrabelag schlichtweg nil. Und nil kann nicht mit dem String “ hinzu.“ verknüpft werden.
Aufgabe 3: Hast Du eine Idee, wie wir diesen Fehler verhindern können? Es kann ja sein, dass jemand eine einfache Margherita ohne Extrabelag wünscht. Zwei Tipps: Du braucht eine bedingte Ausführung per if
. Und Du machst Dir die Tatsache zu Nutze, dass nil
wie false
bewertet wird.
function pizza_backen(extrabelag) print("Teig ausrollen") print("Tomatensoße hinzu") if extrabelag then print(extrabelag .. " hinzu") end print("geriebenen Käse hinzu") print("backen") end
Aufgabe 4: Schreibe nach dem Muster der Lösung von Aufgabe 2 eine Funktion, die zwei Extrabeläge auf die Pizza bringen kann. Ein Hinweis dazu: mehrere Argumente werden einfach mit Kommas getrennt.
function pizza_backen(extrabelag, extrabelag2) print("Teig ausrollen") print("Tomatensoße hinzu") if extrabelag then print(extrabelag .. " hinzu") end if extrabelag2 then print(extrabelag2 .. " hinzu") end print("geriebenen Käse hinzu") print("backen") end
Funktionen mit Rückgabewert
Wir können Funktionen grob gesagt in zwei Kategorien einteilen:
- Funktionen, die etwas machen
- Funktionen, die einen Wert zurückliefern
Die Funktion print()
und unsere selbstgestrickte Funktion pizza_backen()
gehören zur ersten Kategorie. Sie geben etwas auf der Konsole aus.
Die Funktionen type()
oder tonumber()
gehören zur zweiten Kategorie: type()
liefert den Typen des übergebenen Arguments als String. tonumber()
versucht, einen übergebenen String in eine Zahl umzuwandeln:
> type(10) number > tonumber("42") 42 > tonumber("sieben") nil
Die Funktion plus_sieben
Im folgenden zeigen wir, wie ihr selber Funktionen schreibt, die einen Wert zurückliefert. Das erste Beispiel ergibt praktisch nicht besonders viel Sinn, ist aber anschaulich. Die Funktion plus_sieben()
soll zum Argument sieben hinzuzählen, und das Ergebnis zurückliefern:
function plus_sieben(x) return x + 7 end
Das, was hinter dem return steht, ist schlichtweg der Rückgabewert der Funktion.
Die Anwendung auf der Konsole sieht dann so aus:
$ plus_sieben(3) 10 $ plus_sieben(7) 14 $ plus_sieben(-5) 2
Das Beispielprogramm auf repl.it
Aufgabe 5: Schreibe nach dem Muster von plus_sieben()
folgende drei Funktionen und teste sie auf der Konsole:
plus_drei()
, minus_fuenf()
, mal_zehn()
, geteilt_durch_drei()
function plus_drei(x) return x + 3 end function minus_fuenf(x) return x - 5 end function mal_zehn(x) return x * 10 end function geteilt_durch_drei(x) return x / 3 end
Aufgabe 6: Schreibe eine Funktion, die true
zurückliefert, wenn das Argument x
eine Zahl (type(x) == "number"
) ist, sonst false
. Teste die Funktion auf der Konsole.
function ist_zahl(x) return type(x) == "number" end
Anmerkung: Anfänger tendieren oft dazu, diese Lösung mit einem wesentlich komplizierteren Konstrukt in folgender Art zu lösen:
if type(x) == "number" then return true else return false end
Das ist zwar nicht falsch, aber zu umständlich. type(x) == "number"
ist ja bereits das, was wir wissen und zurückliefern wollen.
Aufgabe 7: Schreibe eine Funktion ist_dazwischen(wert, von, bis)
, die true
zurückgibt, wenn wert
genau zwischen von
und bis
ist, sonst false
. Teste die Lösung auf der Konsole.
function ist_dazwischen(wert, von, bis) return wert > von and wert < bis end
Mehrere returns in einer Funktion
In vielen Situationen ist es sinnvoll, mehrere returns im Körper einer Funktion stehen zu haben. Ein Beispiel ist die folgende Funktion begrenze(wert, von, bis)
. Sie gibt den wert
unverändert zurück, wenn er zwischen von
und bis
liegt. Liegt der unter von
, dann soll von
zurückgegeben werden. Liegt wert
über bis
, soll bis
zurückgegeben werden. Solche oder ähnliche Funktionen werden oft benötigt, wenn es etwa darum geht, in einem Spiel zu verhindern, dass ein Spielobjekt (ein Ball oder ähnliches) das Spielfeld verlässt.
$ begrenze(5, 0, 10) 5 $ begrenze(-3, 0, 10) 0 $ begrenze(20, 0, 10) 10
Der Code sieht so aus – hier haben wir drei Stellen, an denen der Funktionskörper per return
verlassen werden kann:
function begrenze(wert, von, bis) if wert < von then return von end if wert > bis then return bis end return wert end
return statt break
Vielleicht ist es Dir schon aufgefallen: return
und break
haben eine gewisse Ähnlichkeit: Während break
den Sprung aus einer Schleife veranlasst, sorgt return
für das Verlassen des Funktionskörpers. Tatsächlich kannst Du mit return
auch eine Funktion verlassen, die keinen Rückgabewert hat. Und in manchen Situationen kannst Du ein return
statt einem break
verwenden. Beides zeigt das folgende Beispiel:
function wuerfel_bis_sechs() while true do zahl = math.random(6) print(zahl) if zahl == 6 then break end end end
Die Funktion soll so lange „würfeln“ (= Zufallszahlen zwischen 1 und 6 erzeugen und auf der Konsole ausgeben), bis eine sechs kommt. In diesem Fall sorgt break
für das Verlassen der while
-Schleife und der Funktion.
Aufgabe 8 (für Fortgeschrittene):Schreibe eine Funktion zahl_eingeben()
. Diese soll ungültige Eingaben (also Eingaben, die sich nicht durch tonumber()
in eine Zahl umwandeln lassen) abfangen und die Nutzerin so lange zur Eingabe einer Zahl auffordern, bis eine gültige Eingabe kommt. Diese soll dann als Zahl zurückgeliefert werden. Speichere den Rückgabewert der Funktion und gib ihn aus.
Bitte gib eine Zahl ein. $ sieben sieben kann ich nicht als Zahl interpretieren. Bitte gib eine Zahl ein. $ Möhrensalat Möhrensalat kann ich nicht als Zahl interpretieren. Bitte gib eine Zahl ein. $ 7 Du hast 7 eingegeben.
function zahl_eingeben() while true do print("Bitte gib eine Zahl ein.") eingabe = io.read() zahl = tonumber(eingabe) if zahl then return zahl else print(eingabe .. " kann ich nicht als Zahl interpretieren.") end end end x = zahl_eingeben() print("Du hast " .. x .. " eingegeben.")
Was wir hier ausgelassen haben
Auch zum Thema Funktionen lässt sich noch sehr viel mehr sagen, als wir in dieser Lektion präsentieren konnten. Einige weiter wichtige Themen wollen wir hier zumindest kurz skizzieren.
Anzahl von Argumenten und Rückagewerten
Lua erlaubt es Funktionen mit einer variablen Anzahl von Argumenten und Rückgabewerten zu schreiben. Für ersteres braucht man Tabellen, die wir erst in der nächsten Lektion kennenlernen. Mehrere Rückgabewerte könnt ihr auf folgende Weise zurückgeben und weiterverarbeiten:
function mehrere() return 1, 2, 3 end a, b, c = mehrere()
Rekursive Funktionen
Es besteht die Möglichkeit, dass eine Funktion sich selbst aufruft. Das klingt verrrückt und würde im folgenden Beispiel auch die Funktion zu einer Endlosschleife und damit zu einem Absturz eures Programms führen:
function selbstaufrufer() selbstaufrufer() end
Aber es gibt Situationen, in denen es sehr sinnvoll ist, solche Funktionen zu schreiben. Klassische Anwendungsbeispiele sind die Berechnung der Fibonacci-Zahlenfolge und die Tiefensuche.
Funktionen zweiter Ordnung
Bereits in der ersten Lektion haben wir gesehen: Auch Funktionen sind Werte – und zwar Werte vom Typ function
:
> type(print) function
Daher könnt ihr unter Bezeichnern Funktionen speichern wie jeden anderen Wert auch. Im folgenden sorgen wir dafür, dass der Bezeichner drucke
auf die Funktion print
verweist. Anschließend können wir drucke
genau so wie print
verwenden:
> drucke = print > drucke("hallo") hallo
Tatsächlich ist die Schreibweise, mit der wir in dieser Lektion Funktionen definiert haben, eine Abkürzung.
function gruss() print("guten tag!") end
ist eigentlich
gruss = function() print("guten tag!") end
Der Bezeichner ist keine Eigenschaft der Funktion, genau so wenig wie bei x = 3
der Bezeichner x
eine Eigenschaft der Zahl 3 ist! x
zeigt auf 3, genau so wie gruss
auf die Funktion zeigt.
Da Funktionen also in Lua ganz normale Werte sind, ist es möglich, Funktionen zweiter Ordnung zu schreiben. Das sind Funktionen, die ihrerseits Funktionen als Argumente verarbeiten oder Funktionen zurückliefern. Das ist in vielen Situationen sehr nützlich, aber ein Thema für fortgeschrittene Lua-Programmiererinnen.
Navigation
Wir bedanken uns bei der Peakboard GmbH für die freundliche Unterstützung bei der Entwicklung dieses Kurses.
Peakboard ist eine All-in-One-Lösung aus Soft- und Hardware, mit der Du Daten aus unterschiedlichen Datenquellen erhebst, auswertest und in Echtzeit auf Bildschirmen visualisierst. Mit der kostenlosen Software, dem Peakboard Designer, gestaltest Du Dein individuelles Dashboard und bindest deine Datenschnittstellen an. Die Hardware, die Peakboard Box, verarbeitet und kommuniziert die Datenströme dezentral und damit ressourcenschonend direkt am Industriearbeitsplatz. Damit sorgst Du für mehr Transparenz und optimierst so ganz einfach deine Prozesse.