Lua Tutorial (Stefan Bosse) [10.2023]

Einführung in die Programmierung mit der Skriptsprache Lua

In diesem Tutorial sollen grundlegendene Kenntnisse in der Programmierung mit der Programmiersprache Lua erworben werden.

Einführung in die Programmierung mit der Skriptsprache Lua
Einführung
Werte, Variablen, Ausdrücke
Elementare Ausdrücke
Mathematische Funktionen
Logische Bit Operationen
String Funktionen und Operationen
Steueranweisungen
Mehrfachauswahl
Funktionen
Datentypen
Funktionen höherer Ordnung
Arrays und Records
Objekte und Klassen
Fehlerbehandlung und Signale
Multi-Tasking
Ko-Routinen

Einführung

Eine Programmiersprache besteht aus eine Menge von Programmieranweisungen die man in folgende Klassen unterteilen kann:

  1. Berechnung
  2. Steuerung
  3. Ein- und Ausgabe
  4. Komposition
  5. Typensystem (wenn überhaupt)

Berechnungen bestehen wiederum aus folgenden Komponenten:

  1. Werten (Konstanten, Literale)
  2. Variablen
  3. Operationen (Addition, usw.)

Eine Variable kann

In den folgenden Beispielen und Aufgaben können Werte und Variableninhalte mit der print Funktion in dem unter dem Code befindlichen Ausgabefenster ausgegeben werden. Hat man komplexere Werte, wie z.B. Datenstrukturen und Arrays, kann man stattdessen die inspect Funktion verwenden.

Dieses Tutorial ist in Snippets unterteilt. Ein Snippet besteht aus einem Texteditorfenster und einem darunter befindlichen Kontroll- und Ausgabefenster. Das Kontrollfenster hat links den Run Knopf, der den Code ausführt, und rechts ein Erase Knopf, der das Ausgabefenster löscht.

Alle Codesnippets werden in einem sogenannten globalen Kontext ausgeführt, d.h., praktisch bilden sie ein Programm! Einzelne Codesnipptes können von der Ausführung vorheriger abhängen.

Daher bei einem Fehler prüfen ob die vorherigen Codesnipptes fehlerfrei ausgeführt wurden!

Alle Eingabefelder (Code und Antworten auf Fragen) können gespeichert und wieder in das interaktive Tutorial geladen werden (Linker und rechter Knopf in der Kopfzeile).

Die Inhalte von Programmcodefeldern können über ein WEB Clipboard mit anderen Teilnehmern geteilt werden (wenn sie gleichzeitig am Tutorial arbeiten). Dazu befindet sich rechts oben in einem Codeeditorfeld ein Kopfsymbol. Das WEB Clipboard Fenster ist spezifisch für dieses Codefeld! Der Copy Befefhl fügt den eigenen Programmcode zum Clipboard hinzu. Code anderer Nutzer kann aus der Liste durch öffnen des Spoiler importiert werden. Der aktuell ausgwählte Code kann durch den Paste Befehl übernommen werden. Code Schnippsel verschwinden nach einer gewissen Zeit wieder aus dem Clipboard (ca. 5 Minuten).

Der Lua Interpreter ist in diese WEB App integriert. Es werden keine externen Programme benötigt! Der Zustand des Interpreters kann momentan nur durch erneutes Laden der Seite zurückgesetzt werden (vorher alle Eingaben speichern).

In Lua werden einzelne Anweisungen entweder durch einen Zeilenumbruch oder ein Semikolon ; getrennt. Einzelne Anweisungen werden immer in der angegebenen Reihenfolge ausgeführt (imperative Programmierung).

Weitere Informationen über Lua gibt es hier:

http://edu-9.de/uploads/documents/lua_tutorial.pdf

Werte, Variablen, Ausdrücke

Werte und Ausdrücke können über den = Operator einer Variablen zugewiesen werden. Lua kennt keine statische Typisierung, d.h., der Variablentyp ist polymorph und wird ausschließlich durch den aktuellen Inhalt (Wert) bestimmt, der sich zur Laufzeit ändern kann.

Variablen sind nur Referenzen auf Werte (Pointer ohne sichtbare Adresse)

Füge in den folgenden Code die print Anweisung für y und z ein.

Variablen und Werte

 ▸ 
 ✗ 
 ≡ 

Elementare Ausdrücke

Ausdrücke bestehen aus Operanden (können auch wieder Ausdrücke sein) und Operatoren:


Ausdrücke

 ▸ 
 ✗ 
 ≡ 

Man unterscheidet globale und lokale Variablen. Eine globale Variable wird einfach durch eine Zuweisungsanweisung x=v eingeführt. Eine lokale Variable wird durch die Anweisung local x=v eingeführt. Der Initialwert v ist optional. In Funktionen immer nur lokale Variablen definieren!

Lokale und globale Variablen

 ▸ 
 ✗ 
 ≡ 

Gebe in obigen Beispiel den Wert von x1 in foo2 aus. Was passiert? Und wenn x2 ausgegeben wird? Welchen Wert hat die globale Variable x1 nach der Ausführung von foo1?


Lokale Variablen können in Funktionen aber auch in Anweisungsblöcken (Schleifen, Bedingte Verzweigung) definiert und benutzt werden.

Mathematische Funktionen

Die math Tabelle stellt eine Vielzahl von mathematischen Operationen zur Verfügung.

math.abs(x)    -- Aboslutwert
math.max(a,b)  -- Maximum von Werten
math.min(a,b)  -- Minimum von Werten
math.sqrt(x)   -- Wurzel zur Basis 2
math.sin(x)    -- Trig. Sinusfunktion (arg: 2PI)
math.cos(x)    --       Kosinus
math.tan(x)    --       Tangens
math.asin(x)   -- Inverse trig. Funktionen ...
math.acos(x)
math.atan(x)
math.log(x)    -- Log. zur Basis 2
math.log10(x)  -- Log. zur Basis 10
math.exp(x)    -- e^x^ 
math.ceil(x)   -- Rundung auf ganzahlige Werte
math.floor(x)
math.random()  -- Gleichevrteilter Pseudozufallszahlengen. [0,1]
math.randomseed(x) -- Initialisierung der PZZG Sequenz
...

Anders als z.B. in javaScript oder C-basierten Programmen startet jeder Lua Prozess / Thread mit der gleichen Initialisierung des Pseudozufallszahlengenerators!.

Partielle Abhilfe: math.randomseed(os.time()) (Achrtung: os.time liefert nur Sekundenauflösung)

Aufgabe. Berechne den Mittelwert und die Standardabeichung einer großen Folge von PZZG Werten. Benutze Schleifen (siehe Kapitel [Steueranweisungen]). Code:

 ▸ 
 ✗ 
 ≡ 

Logische Bit Operationen

In Lua gibt es keine direkten Bitoperation. Typischerweise wird das bit32 Modul verwendet. Dieses steht auch hier zur Verfügung.

Logische Bit (32 Bit Integer) Operationsn via pervasives Modul

 ▸ 
 ✗ 
 ≡ 

String Funktionen und Operationen

Weitere Information zu Strings gibt es hier: https://www.lua.org/pil/20.2.html

Steueranweisungen

Steueranweisungen dienen zur Beeinflussung des Programmflusses. Man unterscheidet (mit s als Anweisung oder einer Reihe von Anweisungen):

  1. Bedingte Verzweigungen: if e then s1 end oder if e then s1 else s2 end oder if e1 then s1 elseif e2 then s2 .. end
  2. Bedingte Schleifen: while e do s end uns repeat s until e
  3. Zählschleifen: for i=a,b[,step] do s end

Es gibt keine Mehrfachauswahl!

Folgendes Beispiel zeigt die Implementierung der Funktion:


 ▸ 
 ✗ 
 ≡ 

Der Programmfluss von Schleifen ist in folgender Abbildung dargestellt (Flussdiagramme).

#loops

Im folgenden Beispiel wird mit einer Turtlegraphik ein Oktagon gezeichnet. Ersetze den Code mit einer Zählschleife die die gleiche Figur zeichnet.


Beispiel Turtlegrafik

 ▸ 
 ✗ 

Mehrfachauswahl

Es gibt (im Modul pervasives) eine funktionale Implementierung einer Mehrfachauswahl:

funktionale Mehrfachauswahl (Ergänzung durch Modul pervasives)

 ▸ 
 ✗ 
 ≡ 

function Switch(c)
  local swtbl 
  swtbl = {
    casevar = c,
    caseof = function (code)
      local f
      if swtbl.casevar ~= nil then
        f = code[swtbl.casevar] or code.default
      else
        f = code.missing or code.default
      end
      if type(f) == "string" then
        f = code[f] or code.default
      elseif type(f) == "number" then
        f = code[f] or code.default
      end
      if f then
        if type(f)=="function" then
          return f(swtbl.casevar,self)
        else
          error("case "..tostring(swtbl.casevar).." not a function ("..type(f)..")")
        end
      end
    end
  }
  return swtbl
end

Funktionen

Funktionen dienen der Programmkomposition und der Wiederverwendung von Code. Man unterscheidet:

  1. Funktionsdefinition
  2. Funktionsapplikation

Eine Funktionsdefinition besteht aus:

  1. Einem Namen (wobei anonyme Funktionen als sog. Lambda-ausdrücke zur Verfügung stehen);
  2. Keinem, einen, oder mehrere Fuktionsparameter;
  3. Einem Funktionsrumpf der eine Berechnung (mit den Parametern) durchführt UND/ODER prozedural wirkt (d.h. Ein- und Ausgabe, globalen Speicher verändern)
  4. Einem Rückgabewert (auch bei Prozeduren: nil!), der mit der Anweisung return zurückgegeben wird.

Die Applikation ist einfach: Name der Funktion mit folgender Liste von Argumenten, d.h., foo(1,2,3). Füge in folgenden Beispielen Funktionsaufrufe (Applikationen) von foo und foo2 ein und gebe die Werte mit print aus.

Funktionsdefiniton

 ▸ 
 ✗ 
 ≡ 

Frage. Was passiert wenn die Anzahl der Argumente kleiner oder größer als die Anzahl der Funktionsparameter ist? Teste es mit foo und foo2

Frage. Was ist Funktionsrekursion?

Wenn eine Funktion sich in ihrem Funktionsrumpf selbst aufruft!

Wende die Funktionen fac und facsq für die Werte 1,10,20 an und gebe die Werte aus. Ein kleiner Tipp: Obwohl Lua eigentlich nur Fliesskommawerte (quasi reelle Zahlen) verarbeitet (und keine ganzen), unterscheidet der Lua Interpreter der in diese WEB App integriert ist (aus einem mir nicht bekannten Grund) ganze Zahlen (Integer, 1,2,3,..) und Fliesskommazahlen (Float, 1.0, 1.1, 1.2, ..). Probiere einmal als Wert 10 und dann 10.0 ...

Funktionskomposition

 ▸ 
 ✗ 
 ≡ 

Frage. Wie kann die Berechnung von facsq optimiert werden (Annahme: Berechnung von fac ist rechenintensiv!)

In der folgenden Aufgabe soll die Fibonacci Funktion implementiert und getestet werden. Benutze Funktionsrekursion!

#fib

Fibonacci Funktion

 ▸ 
 ✗ 
 ≡ 

Frage. Welche Probleme und Nachteile können bei der Funktionsrekursion auftreten? Wie sieht es mit Parallelisierung aus?

Datentypen

Variablen in Lua sind polymorph. Somit auch Funktionen, d.h. deren Funktionsparameter und der Rückgabewert! Es gibt nur wenige Grunddatentypen in Lua:

  1. number
  2. boolean
  3. string
  4. table
  5. function
  6. (userdata)

That's all folks!

Der Datentyp eines Wertes (einer Variablen) kann mit der type Funktionen ermittelt werden (gibt einen Stringwert zurück).

Datentypen

 ▸ 
 ✗ 
 ≡ 

Teste und gebe die Datentypen der Werte von x, y, und sq mit der print(type(v)) Anweisung aus.

Ja richtig erkannt: Funktionen sind Werte in Lua!!! Funktionen können in Variablen enthalten sein, an Funktionen als Argument übergeben werden, und von Funktionen als Wert zurückgegebn werden!

Funktionen höherer Ordnung

Da Funktionen auch Werte (erster Ordnung) sind, kann man damit viele Berechnungen vereinfachen bzw. abstrahieren.

Beispiel ist die Berechnung eines bestimmten Integrals einer beliebigen Funktion. Man müsste für jede Funktion die Integralberechnung immer wieder definieren. Es geht auch anders:


 ▸ 
 ✗ 
 ≡ 

Füge eine Integralfunktion isqx für die Quadratfunktion x • x und isinx für die Sinusfunktion math.sin hinzu und teste exemplarisch.

Arrays und Records

Um Daten namentlich zu binden verwendet man Arrays und Records (Datenstrukturen). Der Unterschied zwischen beiden ist gering. Die Elemente von Arrays werden über einen numerischen Index (1,2,..) ausgewählt, die von Records durch die vom Programmierer gegebenen Namen der Elemente. Anders als in Programmiersprachen mit statischen Typsystem (C, Java) können auch Arrays in Lua mehrsortig sein, d.h. die Elemente können unterschiedliche Datentypen haben!

Arrays und Records sind in Lua sog. Tabellen. Genau genommen sind es beide Hashtabellen. Arrays und Records werden in Lua wie folgt definiert (vi sind Werte oder Ausdrücke):

-- Array
local a = {v1,v2,..,vn}
-- Record
local r = {e1=v1, e2=v2, .. , en:vn}

Es gibt zwei Arten auf die Elemente von Records (Tabellen) zuzugreifen:

x=r['ei']
x=r.ei

Auf Arrayelemente wird durch einen numerischen Index zugegriffen, wobei der Startindex (erstes Element) immer 1 ist:

local index = 1
x=a[index]

Die Länge eines Arrays kann durch den #a Operator ermittelt werden (d.h. die Anzahl der Elemente).

Weitere Informationen zu Arrays gibt es hier: https://www.lua.org/pil/11.1.html sowie zu mehrdimensionalen Arrays https://www.lua.org/pil/11.2.html

Folgende Beispiele sollen dies verdeutlichen:


 ▸ 
 ✗ 
 ≡ 

Welche Werte haben die einzelnen Variablen axi und rxi? Lese nach was Hashtabellen sind (Wikepedia https://de.wikipedia.org/wiki/Hashtabelle) und erkläre die Ergebnisse.

Arrays und Records gehören zur prozeduralen Programmierung. D.h., man definiert Arrays/Records getrennt von Funktionen die die Arrays/Records verarbeiten. Beispiele:


 ▸ 
 ✗ 
 ≡ 

Was ist das Problem bei dieser getrennten Programmierung von Daten und Funktionen die auf diese Daten angewendet werden?

Betrachte folgendes Beispiel:


 ▸ 
 ✗ 
 ≡ 

Objekte und Klassen

Objekte binden Daten und Funktionen (hier genannt Methoden). Die Methoden arbeiten nur auf Daten ihres Objekts. Objekte werden von Klassenprototypen erzeugt.

In Lua gibt es keine Klassen und somit auch keine Objekte. Diese müssen mit Tabellen "simuliert" werden, d.h. die Bindung von Prototypenfunktionen an Tabellen.

Da Funktionen Werte sind kann man erst einmal Records (also Tabellen) direkt verwenden. Ist das schon ein Objekt nach obiger Definition?


 ▸ 
 ✗ 
 ≡ 

Da die Bindung von Prototypenfunktionen an Tabellen etwas kompliziert ist gibt es eine einfache Funktion um eine leere Klasse als Tabelle zu definieren: class(). Es können dann "Methoden" (also Funktionsprototypen) hinzugefügt werden und beliebig viele Objekte von dieser Klasse erzeugt werden:


 ▸ 
 ✗ 
 ≡ 

Erweitere die Klasse um die Methoden incr und decr die die Objektvariable x um eins erhöht oder erniedrigt. Teste die Methoden exemplarisch.

Zu beachten ist der Doppelpunkt zwischen Klassenname und Methodenname! Dieser sorgt für eine Bindung der Funktion an das Objekt!

function Class()
  local Class = {}; Class.__index = Class;
  function Class:new(...)
    local self = setmetatable({}, Class)
    if Class.init then Class.init(self,...) end
    return self
  end
  return Class
end

Aufgabe. Implementiere eine Klasse die einen Datenlogger implementiert. Dazu soll es die Methoden log(level,msg), clear(level), filter(level,tag) und print(level) geben. Es gibt verschiedene Informationslevel (optional, level). Bei der Filter Funktion sind level und ein zu suchendes Stichwort tag in den Nachrichten jeweils optional (aber eines der beiden Argumente muss angegeben werden!). Teste die Klasse mit einem Beispiel. Logger Klasse:

 ▸ 
 ✗ 
 ≡ 

Fehlerbehandlung und Signale

In einem Programm können Fehler auftreten:

Ein Fehler löst ein Signal aus (ein Ausnahmesignal). Wird dieses Signal nicht behandelt, führt dies zum Abbruch des Programms (i.A.). Es gibt aber auch Signale die man verwendet um an einer Stelle des Programms an eine andere "höhergelegene" Stelle zu gelangen. Entweder geplant oder auch um "seitwärts" aus einer Funktion bei einem Fehler (nicht schwerwiegend) wieder zum aufrufenden Kontext zu gelangen.

Programmiersprachen wie Java oder JavaScript bieten Signalbehandlung mittels einer try catch Anweisung, Lua leider nicht direkt/komfortabel (nur mittels der pcall oder xpcall Funktionen).

Ein Signal kann in Lua durch die raise(signal) Anweisung ausgelöst werden. Das Signal ist i.A. ein Stringname.


 ▸ 
 ✗ 
 ≡ 

Frage. Was passiert wenn die Funktion ohne Argument aufgerufen wird? Wie ändert sich das Verhalten bei Aufruf von incr durch pcall(function () .. end)? Was gibt pcall als Wert zurück (mit print testen)?

Es gibt in Lua keine komfortable Möglichkeit Fehlersignale zu behandeln (try-catch). Aber es gibt ergänzend programmatische Funktionen.

Man kann die fehlende try-catch Anweisung aber funktional ergänzen, und man erhält dann folgende Funktioneskette (Definiert im pervasives Modul der lvm oder Fengari Web Lua VM):


 ▸ 
 ✗ 
 ≡ 

local functionType = "function"
function Try (tryBlock)
    local status, err = true, nil, trace
    function traceback(e)
      trace = e;
      return e 
    end
	if type(tryBlock) == functionType then
		status, err = xpcall(tryBlock,traceback)
	end

	local _finally = function (finallyBlock, catchBlockDeclared)
		if type(finallyBlock) == functionType then
			finallyBlock()
		end
		
		if not catchBlockDeclared and not status then
			raise(err)
		end
	end

	local _catch = function (catchBlock)
		local catchBlockDeclared = type(catchBlock) == functionType;

		if not status and catchBlockDeclared then
			local ex = err or "unknown error occurred"
			catchBlock(ex,trace)
		end

		return {
			finally = function(finallyBlock)
				_finally(finallyBlock, catchBlockDeclared)
			end
		}
	end

	return
	{
		catch = _catch,
		finally = function(finallyBlock)
		    _finally(finallyBlock, false)
		  end
	}
end


Multi-Tasking

Es gibt verschiedene Ebenen des Multi-taskings:

  1. Verteilte Prozesse (ein Prozess == eine VM Instanz, einzelne Instanzen sind völlig isoliert, Kommunikation nur über Nachrichten)
  2. Parallele Prozesse (ein Prozess == eine VM Instanz, einzelne Instanzen sind völlig isoliert, Kommunikation nur über Nachrichten)
  3. Parallele Prozesse (ein Prozess == ein Thread == eine VM Instanz, einzelne Instanzen sind teilweise isoliert und können über geteilten Speicher und atomare Operation kommunizieren)
  4. Ko-Routine (eine Ko-Routine == eine Funktion innerhalb einer VM Instanz)

Ko-Routinen

Im Gegensatz zu JavaScript unterstützt Lua direkt Ko-Routinen mit kooperativen Scheduling:


 ▸ 
 ✗ 
 ≡ 

Ko-Routinen werden immer sequenziell ausgeführt und niemals präemptiv (unterbrechend), d.h. Lua unterstützt nur sog. asymmetrische Ko-Routinen. Die Kontrolle und das Wechslen zwischen Ko-Routinen erfolgt explizit durch coroutine.yield() oder ggf. implizit durch IO Funktionen (die dann die aktuelle Ko-Routine wechseln).

Operationen auf Ko-Routinen:

  1. coroutine.yield() gibt die Ausführung der aktuellen Ko-Routine ab (und es kann eine andere ausgeführt werden)
  2. coroutine.resume(co) nimmt die Ausführung einer bestimmtem Ko-Routine wieder auf (genau nach dem letzten yield Punkt).
  3. coroutine.status(co) liefert den Status einer Ko-Routine (z.B. suspended).

Ko-Routinen teilen sich die gleiche VM und den gleichen Programmkontext und können daher direkt Daten via Variablen austauschen.

Der yield Operation kann ein Wert (Argument) übergeben werden, welches dann von der nächsten resume Operation empfangen werden kann:

in co1: coroutine.yield(value)
local status, value = coroutine.resume(co2)

Aufgabe. Implementiere ein klassisches Produzenten-Konsumenten Programm mit zwei Ko-Routinen, d.h., im nachfolgenden Programm die Funktionen receive und send.


 ▸ 
 ✗ 
 ≡ 



Hilfe



Einreichung (Assignment #01-49750)



Prüfen



Bewerten (Lehrer)




Created by the NoteBook Compiler Ver. 1.23.1 (c) Dr. Stefan Bosse (Mon Oct 23 2023 15:46:39 GMT+0200 (CEST))