9.8 Metamethoden

Metamethoden können verwendet werden, um zu definieren, wie Hollywoods Operatoren sich mit Tabellen verhalten soll. Normalerweise können Sie Hollywoods Operatoren nicht mit Tabellen verwenden. Beispielsweise ist die folgendes nicht möglich:

 
table_A = {1, 2, 3, 4, 5}
table_B = {5, 4, 3, 2, 1}
result = table_A + table_B   ; erzeugt Compilerfehler!

Der obige Code versucht table_A mit table_B zu addieren, aber das funktioniert nicht, weil Tabellen Zufallsdaten enthalten können (Funktionen, Untertabellen, Zeichenketten, etc.). So gibt es keine Möglichkeit zu sagen, wie der Add-Operator sich bei einer Tabelle verhalten soll. Dies ist der Moment, wo Metamethoden ins Spiel kommen. Mit Metamethoden können Sie festlegen, wie ein Operator sich zu verhalten hat, wenn er eine Tabelle als Operanden enthält. Mit anderen Worten, mit Metamethoden können Sie eine Funktion definieren, die ausgeführt wird, wenn ein Operator mit einer Tabelle verwendet wird. Diese Funktion berechnet dann das Ergebnis.

Metamethoden sind keine globalen Einstellungen, sie sind für jede Tabelle privat. Wenn Sie eine Tabelle erstellen, wird sie keine Metamethoden haben. Versuchen Sie einen Operator auf dieser Tabelle zu verwenden, wird er fehlschlagen Für das Zuweisen einer Metamethode an einer Tabelle müssen Sie den Befehl SetMetaTable() verwenden. Ein Metatabelle ist eine Tabelle, die einen Satz von Metamethoden enthält. SetMetaTable() akzeptiert zwei Argumente: Das erste Argument ist der Name der Tabelle, der Sie eine Metamethode zuweisen wollen, und das zweite Argument ist die eigentliche Metatabelle, d.h. die Tabelle, die die Metamethoden enthält.

Lassen Sie uns nun ein Beispiel anschauen. Wir werden den Code von oben so mit Metamethoden neu schreiben, dass wir die beiden Tabellen addieren können.

 
mt = {}  ; erstellt unsere Metatabelle

Function mt.__add(a, b)
    Local sizeA = ListItems(a)   ; Anzahl Elemente in Tabelle A
    Local sizeB = ListItems(b)   ; Anzahl Elemente in Tabelle B
    Local result = {}            ; erstellt resultierende Tabelle

    For Local k = 0 To Min(sizeA, sizeB) - 1
        result[k] = a[k] + b[k]  ; zufügen der addierten Elemente
    Next

    Return(result)   ; resultierende Tabelle zurückgeben
EndFunction

table_A = {1, 2, 3, 4, 5}
table_B = {5, 4, 3, 2, 1}

SetMetaTable(table_A, mt)   ; weist "mt" der Tabelle table_A...
                            ; ...als Metatabelle zu

result = table_A + table_B

Die resultierende Tabelle wird fünf Elemente aufweisen, die alle auf 6 gesetzt sind. Nun schauen wir, was der Code oben gemacht hat. Zuerst erstellen wir eine leere Tabelle, die für uns als Metatabelle dient. Dann fügen wir eine Funktion namens __add (zwei Unterstriche verwenden) an diese Tabelle. Diese Funktion wird die Metamethode für den + Operator. Beachten Sie, dass wir den Namen __add für diese Funktion verwenden müssen, weil Hollywood anhand des Funktionsnamen den Operator erkennt, die die Metamethode verkörpert. Mit __add als Name wird eine Metamethode für den Add-Operator (+) definiert. Der Code in unserer Metamethode berechnet die Länge der beiden Tabellen, addiert zwei Tabellenelemente und speichert sie in einer Ergebnistabelle, die sie zurückgibt.

Beachten Sie, dass die Umsetzung unserer __add Metamethode oben erfordert, dass beide Argumente Tabellen sind. Und die Tabellen dürfen nur Zahlen enthalten (oder Zeichenketten, die in Zahlen umgewandelt werden können). Z.B. die folgenden Ausdrücke würden mit der oben definierten Metamethode nicht funktionieren:

 
result = table_A + 10       ; --> Fehler, weil "10" ist keine Tabelle
result = table_A + "Hello"  ; --> gleicher Fehler

Natürlich ist es möglich, Metamethoden zu schreiben, die diese Situationen umgehen kann. Sie müssen nur die Typen der Parameter überprüfen, die auf Ihre Metamethode übergeben werden. Dann können Sie abhängig vom Variablentyp benutzerdefinierte Aktionen ausführen.

Jetzt haben wir die Metamethode für den Add-Operator (+) kennengelernt. Natürlich können Sie eine Metamethode für jeden anderen Hollywood-Operator anwenden. Sie können auch Metamethoden für alle relationalen Operatoren erstellen (= <> <> <=> =), so dass Sie Tabellen direkt untereinander vergleichen können. Alles was Sie wissen müssen, ist der richtige Name für den Operator der Metamethode, so dass Sie ihn installieren können. Hier ist eine Liste aller verfügbaren Metamethoden und die entsprechenden Operatoren:

Wie Sie sehen können, gibt es keine Metamethoden für die >, >= und <> Operatoren. Dies liegt daran, weil Hollywood durch neu formulierte Bedingungen der bekannten Operatoren diese Resultate auf folgendem Weg liefern kann:

 
a <> b      ist das gleiche wie     Not (a = b)
a > b       ist das gleiche wie     b < a
a >= b      ist das gleiche wie     b <= a

Wenn Sie zwei Tabellen miteinander vergleichen möchten, die beide mit Metamethoden verknüpft sind, ohne die __eq Metamethode aufrufen, müssen Sie den Befehl RawEqual() verwenden. Dieser Befehl wird beide Tabellen nur als Referenz vergleichen, ohne die Metamethoden aufzurufen.

Abweichende Metatabellen mit Binäroperatoren

Wie Sie sehen können, hat jede Tabelle seine eigene private Metatabelleeinstellung. Werden binäre Operatoren verwendet, kann es vorkommen, dass die beiden Operanden nicht die gleichen Metatabelle verwenden. Wie wählt jetzt Hollywood die Metatabelle für die Operanden aus? Dies hängt von verschiedenen Bedingungen ab:

  1. Wenn der Operator ein Vergleichsoperator (= <> ist <<=>> =), wird die Metamethode nur dann aufgerufen, wenn die beiden genannten Tabellen die gleiche Metatabelle verwenden. Wenn sie unterschiedlich Metatabellen haben, wird der Vergleich fehlschlagen.

  2. Wenn der Operator eine arithmetischer Operator, ein bitweise Operator oder der Zeichenfolgeverkettungsoperator (..) ist, wird Hollywood zunächst in Operand A nach einer Metatabelle sehen. Wenn Operand A ein Metatabelle hat, dann wird dies Metatabelle verwendet. Wenn Operand A keine Metatabelle hat, wird Hollywood in Operand B nachschauen. Wenn Operand B eine Metatabelle besitzt, wird sie verwendet. Wenn keine der Operanden eine Metatabelle vorweisen kann, wird eine Fehlermeldung ausgegeben.

Einschränkungen der relationalen Metamethoden

Sie haben oben bereits gelesen, dass die relationale Metamethoden nur dann funktionieren, wenn die beiden Operanden die gleichen Metatabelle verwenden. Jedoch gibt es eine weitere Einschränkung bei der Verwendung von relationalen Metamethoden: Sie werden nur dann aufgerufen, wenn die zwei Operanden Tabellen sind. Es ist nicht möglich, eine Tabelle mit einer Nummer oder einer Zeichenfolge mit einer Tabelle zu vergleichen etc. Die arithmetischen und bitweise Metamethoden können mit jedem Variablentyp arbeiten, aber die relationale Metamethoden sind auf das Vergleichen von Tabellen beschränkt.

Erweiterte Metamethoden

Bisher haben wir nur die relationalen, arithmetischen, bitweise und verkettete Metamethoden angeschaut. Es gibt jedoch ein paar mehr Metamethoden, die Sie verwenden können, nämlich __index, __newindex, __call und __tostring. Hier ist eine detaillierte Beschreibung dieser Metamethoden:

__index:
Diese Metamethode wird aufgerufen, wenn Sie versuchen, einen Tabellenindex zu lesen, der nicht existiert. Diese Metamethode könnte verwendet werden, um einen Standardwert für alle nicht initialisierten Tabellenfelder zu erstellen. Normalerweise wird Hollywood fehlschlagen, wenn Sie von nicht initialisierten Feldern lesen. Dieses Verhalten könnte geändert werden, wenn Sie diese Metamethode verwenden. Hier ist ein Codeschnipsel, die den Standardwert auf 0 setzt:

 
mt = {}
Function mt.__index(t, idx)
    Return(0)
EndFunction

t = {x = 10, y = 20}
SetMetaTable(t, mt)
NPrint(t.x, t.y, t.z)  ; --> gibt 10 20 0 aus

Ohne unsere Metatabelle würde der Aufruf von NPrint() scheitern, weil z wurde nicht initialisiert. Durch die Verwendung der Metatabelle jedoch wird z wieder automatisch auf 0 gesetzt, weil es nicht existiert.

Manchmal kann es notwendig werden, aus einer Tabelle ohne Metamethoden zu lesen. Sie können dies mit dem RawGet() Befehl tun. RawGet() wird nie eine Metamethode aufrufen. Wenn ein Index nicht vorhanden ist, wird Nil zurückgegeben.

__newindex:
Diese Metamethode wird aufgerufen, wenn Sie versuchen, einen Wert auf einen Tabellenindex zu schreiben, der noch nicht in der Tabelle vorhanden ist. Sie könnten diese Metamethode zum Beispiel verwenden, um nur lesbare Tabellen zu erstellen. Der folgende Code wird ein Fehler ausgegeben, wenn Sie versuchen, in eine nur lesbare Tabelle zu schreiben:

 
mt = {}
Function mt.__newindex(t, idx, val)
    NPrint("Blocked writing", val, "at index", idx)
EndFunction

t = {x = 10, y = 20}
SetMetaTable(t, mt)
t.z = 45    ; --> Blockiert Schreib 45 bei Index z

Der obige Code setzt die Tabelle t unter Schreibschutz. Sie können keine Änderungen an der Tabelle vornehmen.

Manchmal kann es notwendig werden, in eine Tabelle ohne Metamethoden zu schreiben. Sie können dies mit dem RawGet() Befehl tun. RawGet() wird nie eine Metamethode aufrufen. Man könnte sogar mit dem RawSet() Befehl in schreibgeschützte Tabellen schreiben.

__call:
Diese Metamethode wird aufgerufen, wenn Sie versuchen, eine Tabelle aufzurufen. Normalerweise schlägt der Versuch fehl, weil Tabellen offensichtlich nur Typen von Datenspeicher und keine Funktionen sind. Es gibt jedoch Fälle, in denen es nützlich sein kann, wenn Sie auch eine Tabelle aufrufen könnten. Das folgende Beispiel zeigt eine Metamethode, die den Durchschnitt aller Tabellenwerte berechnet:

 
mt = {}
Function mt.__call(t)
    Local c = ListItems(t)
    Local sum = 0

    For Local k = 0 To c - 1 Do sum = sum + t[k]

    Return(sum / c)
EndFunction

t = {10, 23, 45, 5, 107, 45, 18, 46}
SetMetaTable(t, mt)
NPrint(t())   ; --> 37.375

Der obige Code gibt 37,375 zurück, welcher der Mittelwert der acht gespeicherten Werte in der Tabelle t ist.

__tostring:
Diese Metamethode wird durch Befehle wie Print() oder DebugPrint() verwendet. Normalerweise, wenn Sie eine Tabelle an Print() übergeben, werden Sie so etwas wie "Tabelle: 1acd432f" erhalten. Hollywood handhabt dies intern so, um auf die Tabelle zu verweisen und ist offensichtlich für Sie nicht sinnvoll. Jedoch mit der Metamethode __tostring können Sie dieses Verhalten leicht ändern. Hier ist eine Metamethode, die eine Zeichenkettendarstellung einer Tabelle erstellt:

 
mt = {}
Function mt.__tostring(t)
    Local r$
    For Local k=0 To ListItems(t)-1 Do r$=r$..t[k].." "
    Return(r$)
EndFunction

t = {"Jeff", "Andy", "Mike", "Dave"}
SetMetaTable(t, mt)
NPrint(t)   ; --> Jeff Andy Mike Dave

Der obige Code gibt "Jeff Andy Mike Dave" aus, weil unsere __tostring Metamethode einfach alle Elemente der Tabelle verkettet hat.


Navigation zeigen