2.8 Metatabellen Korrektur einreichen Original anzeigen

Jeder Wert in Lua kann eine Metatabelle besitzen. Diese Metatabelle ist eine gewöhnliche Lua-Tabelle, welche das Verhalten des originalen Wertes bei bestimmten Operationen bestimmt. Sie können diverse Aspekte des Verhaltens von Operationen auf Werte durch das Setzen spezifischer Felder in dessen Metatabelle verändern. Wenn beispielsweise ein nicht-numerischer Wert der Operand einer Addition ist, sucht Lua nach einer Funktion im Feld "__add" in dessen Metatabelle. Wenn es eine findet, ruft Lua diese Funktion auf, um die Addition durchzuführen.

Wir nennen die Schlüssel einer Metatabelle Ereignisse und die Werte Metamethoden. Im vorherigen Beispiel ist das Ereignis "add" und die Metamethode ist die Funktion, welche die Addition durchführt.

Sie können die Metatabelle jedes Wertes über die getmetatable-Funktion abfragen.

Sie können die Metatabellen von Tabellen über die setmetatable-Funktion ersetzen. Sie können die Metatabellen anderer Typen von Lua nicht ändern (außer über die Debugbibliothek); Sie müssen hierfür die C-API verwenden.

Tabellen und alle Benutzerdaten haben individuelle Metatabellen (obwohl mehrere Tabellen und Benutzerdaten deren Metatabellen teilen können). Werte aller anderen Typen teilen sich eine einzige Metatabelle pro Typ; d. h. es gibt eine einzige Metatabelle für alle Zahlen, eine für alle Zeichenketten etc.

Eine Metatabelle steuert, wie sich ein Objekt bei arithmetischen Operationen, Sortierungsvergleichen, Konkatenierungen, Längenoperationen und der Indizierung verhält. Eine Metatabelle kann darüber hinaus eine Funktion definieren, welche aufgerufen wird, wenn Benutzerdaten automatisch bereinigt werden. Für jede dieser Operationen assoziiert Lua einen spezifischen Schlüssel, welcher Ereignis genannt wird. Wenn Lua eine dieser Operationen auf einen Wert anwendet, prüft es, ob dieser Wert eine Metatabelle mit dem entsprechenden Ereignis besitzt. Falls dies der Fall ist, steuert der mit diesem Schlüssel verknüpfte Wert (die Metamethode) , wie Lua diese Operation durchführt.

Metatabellen steuern die im Folgenden aufgelisteten Operationen. Jede Operation wird durch ihren entsprechenden Bezeichner identifiziert. Der Schlüssel für jede Operation ist eine Zeichenkette mit zwei vorangestellten Unterstrichen ('__'); der Schlüssel für die Operation "add" ist beispielsweise die Zeichenkette "__add". Die Semantik dieser Operationen werden durch eine Lua-Funktion, welche beschreibt, wie der Interpreter Operationen ausführt, besser erklärt.

Der hier gezeigte Lua-Code dient lediglich der Illustration; das echte Verhalten ist fest im Interpreter kodiert und ist wesentlich effizienter als diese Simulation. Alle Funktionen, welche in diesen beschreibungen verwendet werden (rawget, tonumber etc.) sind unter § 5.1 beschrieben. Insbesondere um die Metamethode eines gegebenen Objekts zu erhalten, benutzen wir den Ausdruck …

metatable(obj)[event]

Dies sollte wie folgt gelesen werden:

rawget(getmetatable(obj) or {},event)

Das bedeutet, dass der Aufruf einer Metamethode keine anderen Metamethoden aktiviert und der Zugriff auf Objekte ohne Metatabellen nicht fehlschlägt (sondern einfach in nil resultiert).

  • "add": Die +-Operation.

    Die unten angegebene Funktion getbinhandler definiert, wie Lua den Handler für eine binäre Operation auswählt. Zuerst versucht Lua den ersten Operanden. Wenn dessen Typ keinen Handler für die Operation definiert, versucht Lua den zweiten Operanden.

    function getbinhandler(op1,op2,event)
      return metatable(op1)[event] or metatable(op2)[event]
    end

    Durch die Verwendung dieser Funktion ist das Verhalten von op1 + op2

    function add_event(op1,op2)
      local o1,o2 = tonumber(op1),tonumber(op2)
      if o1 and o2 then  -- sind beide Operanden numerisch?
        return o1 + o2   -- '+' ist das primitive 'add'
      else  -- zumindest einer der Operanden ist nicht numerisch
        local h = getbinhandler(op1,op2,"__add")
        if h then
          -- rufe den Handler mit beiden Operanden auf
          return (h(op1,op2))
        else  -- kein Handler verfügbar: Standardverhalten
          error(···)
        end
      end
    end
  • "sub": Die --Operation. Verhalten analog zur "add"-Operation.
  • "mul": Die *-Operation. Verhalten analog zur "add"-Operation.
  • "div": Die /-Operation. Verhalten analog zur "add"-Operation.
  • "mod": Die %-Operation. Verhalten analog zur "add"-Operation, mit o1 - floor(o1/o2)*o2 als primitive Operation.
  • "pow": Die ^-Operation. Verhalten analog zur "add"-Operation, mit pow (aus der mathematischen Bibliothek von C) als primitive Operation.
  • "unm": Die unäre --Operation.
    function unm_event(op)
      local o = tonumber(op)
      if o then  -- Operand numerisch?
        return -o  -- '-' ist das primitive 'unm'
      else  -- der Operand ist nicht numerisch
        -- versuche, einen Handler vom Operanden zu erhalten
        local h = metatable(op).__unm
        if h then
          -- rufe den Handler mit dem Operanden auf
          return (h(op))
        else  -- kein Handler verfügbar: Standardverhalten
          error(···)
        end
      end
    end
  • "concat": Die ..-Operation (Konkatenierung).
    function concat_event(op1,op2)
      if (type(op1) == "string" or type(op1) == "number") and
         (type(op2) == "string" or type(op2) == "number") then
        return op1 .. op2  -- primitive Zeichenketten-Verknüpfung
      else
        local h = getbinhandler(op1,op2,"__concat")
        if h then
          return (h(op1, op2))
        else
          error(···)
        end
      end
    end
  • "len": Die #-Operation:
    function len_event(op)
      if type(op) == "string" then
        return strlen(op)         -- primitive Zeichenketten-Länge
      elseif type(op) == "table" then
        return #op                -- primitive Tabellen-Länge
      else
        local h = metatable(op).__len
        if h then
          -- Handler mit Operand aufrufen
          return (h(op))
        else  -- kein Handler verfügbar: Standardverhalten
          error(···)
        end
      end
    end

    S. § 2.5.5 für eine Beschreibung der Länge einer Tabelle.

  • "eq": Die ==-Operation. Die Funktion getcomphandler definiert, wie Lua eine Metamethode für Vergleichsoperationen auswählt. Eine Metamethode wird nur ausgewhlt, wenn beide Objekte vom selben Typ sind und die selbe Metamethode für die gewählte Operation haben.
    function getcomphandler(op1,op2,event)
      if type(op1) ~= type(op2) then return nil end
      local mm1 = metatable(op1)[event]
      local mm2 = metatable(op2)[event]
      if mm1 == mm2 then return mm1 else return nil end
    end

    Das "eq"-Ereignis ist wie folgt definiert:

    function eq_event(op1,op2)
      if type(op1) ~= type(op2) then  -- verschiedene Typen?
        return false   -- verschiedene Objekte
      end
      if op1 == op2 then   -- primitiv gleich?
        return true   -- Objekte sind gleich
      end
      -- Metamethode versuchen
      local h = getcomphandler(op1,op2,"__eq")
      if h then
        return (h(op1, op2))
      else
        return false
      end
    end

    a ~= b ist äquivalent zu not (a == b).

  • "lt": Die <-Operation.
    function lt_event(op1,op2)
      if type(op1) == "number" and type(op2) == "number" then
        return op1 < op2   -- numerischer Vergleich
      elseif type(op1) == "string" and type(op2) == "string" then
        return op1 < op2   -- lexikographischer Vergleich
      else
        local h = getcomphandler(op1,op2,"__lt")
        if h then
          return (h(op1,op2))
        else
          error(···)
        end
      end
    end

    a > b ist äquivalent zu b < a.

  • "le": Die <=-Operation.
    function le_event(op1,op2)
      if type(op1) == "number" and type(op2) == "number" then
        return op1 <= op2   -- numerischer Vergleich
      elseif type(op1) == "string" and type(op2) == "string" then
        return op1 <= op2   -- lexikographischer Vergleich
      else
        local h = getcomphandler(op1,op2,"__le")
        if h then
          return (h(op1,op2))
        else
          h = getcomphandler(op1,op2,"__lt")
          if h then
            return not h(op2,op1)
          else
            error(···)
          end
        end
      end
    end

    a >= b ist äquivalent zu b <= a. Beachten Sie, dass Lua in Abwesenheit einer "le"-Metamethode "lt" versucht – unter der Annahme, dass a <= b äquivalent zu not (b < a) ist.

  • "index": Der Index-Zugriff table[key].
    function gettable_event(table,key)
      local h
      if type(table) == "table" then
        local v = rawget(table, key)
        if v ~= nil then return v end
        h = metatable(table).__index
        if h == nil then return nil end
      else
        h = metatable(table).__index
        if h == nil then
          error(···)
        end
      end
      if type(h) == "function" then
        return (h(table,key))     -- Handler aufrufen
      else return h[key]           -- oder Operation darauf wiederholen
      end
    end
  • "newindex": Die Index-Zuweisung table[key] = value.
    function settable_event(table,key,value)
      local h
      if type(table) == "table" then
        local v = rawget(table,key)
        if v ~= nil then rawset(table,key,value); return end
        h = metatable(table).__newindex
        if h == nil then rawset(table,key,value); return end
      else
        h = metatable(table).__newindex
        if h == nil then
          error(···)
        end
      end
      if type(h) == "function" then
        h(table, key,value)           -- Handler aufrufen
      else h[key] = value             -- oder Operation darauf wiederholen
      end
    end
  • "call": Wird aufgerufen, wenn Lua einen Wert aufruft.
    function function_event(func,...)
      if type(func) == "function" then
        return func(...)   -- primitiver Aufruf
      else
        local h = metatable(func).__call
        if h then
          return h(func,...)
        else
          error(···)
        end
      end
    end