0Pricing
Lua Academy · レッスン

メタテーブル入門

setmetatable/getmetatableでメタテーブルを設定・取得します。

「メタテーブル入門」はCoddyKit上の無料Lua Academyレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはLua Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Lua Academyコースには全4レッスンが含まれています。

メタテーブルとは

メタテーブルは、別のテーブルの動作を制御する通常のLuaテーブルです。Luaがテーブルに対して実行できない操作(算術演算や、存在しないキーのインデックス参照など)に遭遇すると、メタテーブル内から対応するメタメソッドを探します。これは、演算子オーバーロードやOOPのためのLuaの拡張メカニズムです。

local t = {}
local mt = {__index = function(_, k) return "default_" .. k end}

setmetatable(t, mt)

print(t.foo)    -- default_foo
print(t.bar)    -- default_bar
print(t[1])     -- default_1

setmetatable と getmetatable

setmetatable(t, mt)はtのメタテーブルをmtに設定し、tを返します。getmetatable(t)はメタテーブルを返し、存在しない場合はnilを返します。mt.__metatable = "protected"を設定すると、メタテーブルが変更されないように保護できます。

local t = {x = 10}
local mt = {}

setmetatable(t, mt)
print(getmetatable(t) == mt)   -- true

-- Protect metatable
mt.__metatable = "locked"
print(getmetatable(t))   -- locked
-- setmetatable(t, {})  -- ERROR: cannot change protected metatable

テーブルとしての __index

__indexがテーブルの場合、Luaはそのテーブルから存在しないキーを検索します。これはLuaのプロトタイプ継承の基盤です。__indexにもそのキーがない場合、Luaはそのテーブルのメタテーブルを調べ、さらにチェーンをたどって検索します。

local Animal = {sound = "...", legs = 4}

local Dog = setmetatable({sound = "Woof"}, {__index = Animal})
local Cat = setmetatable({sound = "Meow"}, {__index = Animal})

print(Dog.sound)   -- Woof (own field)
print(Dog.legs)    -- 4 (from Animal)
print(Cat.sound)   -- Meow
print(Cat.legs)    -- 4

関数としての __index

__indexが関数の場合、Luaはその関数をテーブルと存在しないキーを引数にして呼び出します。関数は値を動的に計算したり取得したりできます。__indexからnilを返すと、「キーが見つからない」ことを示します。

local dynamic = setmetatable({}, {
  __index = function(t, k)
    if k:sub(1,4) == "get_" then
      local field = k:sub(5)
      return function(self) return self[field] end
    end
    return nil
  end
})

dynamic.name = "Lua"
local getter = dynamic.get_name
print(getter(dynamic))   -- Lua

rawget と rawset

rawget(t, k)は__indexを発動させずにテーブルのキーを読み取ります。rawset(t, k, v)は__newindexを発動させずにキーを設定します。メタメソッド内で直接テーブルにアクセスし、無限再帰を避けたい場合に、これらを使ってメタメソッドを迂回します。

local logged = setmetatable({}, {
  __newindex = function(t, k, v)
    print("Setting", k, "=", v)
    rawset(t, k, v)  -- bypass __newindex
  end
})

logged.x = 10   -- Setting x = 10
logged.x = 20   -- no log! key now exists in t
print(logged.x) -- 20

メタテーブルを共有する複数のテーブル

1つのメタテーブルを多数のテーブルで共有できます。これはLuaのクラスシステムの仕組みです。クラスのすべてのインスタンスが、メソッドを保持する同じメタテーブルを共有します。メタテーブルへの変更は、すべてのインスタンスに影響します。

local Mt = {
  __index = {
    greet = function(self)
      print("Hello from " .. (self.name or "?"))
    end
  }
}

local a = setmetatable({name="Alice"}, Mt)
local b = setmetatable({name="Bob"},   Mt)

a:greet()   -- Hello from Alice
b:greet()   -- Hello from Bob

__metatableフィールド

mt.__metatableに任意の値を設定すると、getmetatableは実際のメタテーブルではなく、その値を返します。これにより、ユーザーによるメタテーブルの調査や置き換えを防げます。カプセル化やセキュリティに役立ちます。

local secret = setmetatable({}, {
  __metatable = false,    -- hide metatable
  __index = {value = 42}
})

print(secret.value)        -- 42
print(getmetatable(secret)) -- false (not the real mt)
-- setmetatable(secret, {}) -- ERROR

メタテーブルの確認

getmetatable(t)を使うと、オブジェクトが特定のメタテーブルを持っているか確認できます(型チェックに使用します)。__metatableによって実際のメタテーブルが隠される場合があるため、信頼性の高いisinstanceチェックにはrawequalまたはマーカー用フィールドを使用してください。

local MyClass = {}
MyClass.__index = MyClass
MyClass.__metatable = MyClass   -- use self as marker

function MyClass.new(x)
  return setmetatable({x=x}, MyClass)
end

local obj = MyClass.new(5)
print(getmetatable(obj) == MyClass)   -- true
print(obj.x)                           -- 5

テーブル以外の値のメタテーブル

Luaの文字列には、すでにメタテーブル(stringモジュール)が設定されています。そのため、任意の文字列に対してs:upper()を呼び出せます。純粋なLuaから文字列、数値、ブール値にメタテーブルを設定することはできません。設定できるのはテーブルとuserdataだけです。

local s = "hello"
local mt = getmetatable(s)

-- String metatable has __index = string library
print(mt.__index == string)   -- true

-- So all string functions are methods:
print(s:upper())              -- HELLO
print(s:rep(3, "-"))          -- hello-hello-hello

メタテーブルを使ったデバッグ

__tostringメタメソッドを追加すると、テーブルをtostring()やprint()で表示する方法を制御できます。複雑なオブジェクトのデバッグに非常に役立ちます。

local Point = {}
Point.__index = Point
Point.__tostring = function(p)
  return string.format("Point(%g, %g)", p.x, p.y)
end

function Point.new(x, y)
  return setmetatable({x=x,y=y}, Point)
end

local p = Point.new(3, 4)
print(tostring(p))   -- Point(3, 4)
print(p)             -- Point(3, 4)  (print calls tostring)

確認問題

__indexが別のテーブルに設定されたメタテーブルを持つテーブルで、存在しないキーにアクセスすると何が起こりますか?

まとめ:メタテーブル

まとめ:

  • setmetatable(t, mt) / getmetatable(t)
  • __index:テーブルの場合はプロトタイプ検索、関数の場合は動的ディスパッチ
  • rawget/rawsetはメタメソッドを回避します
  • メタテーブルを共有すると、クラスのような動作を実現できます
  • __metatableはメタテーブルを調査から保護します
  • __tostringは出力方法を制御します

よくある質問

「メタテーブル入門」レッスンは無料ですか?

はい。「メタテーブル入門」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Lua Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Lua Academyコースには全4レッスンが含まれています。

「メタテーブル入門」で何を学びますか?

setmetatable/getmetatableでメタテーブルを設定・取得します。 ブラウザで直接実行するハンズオンコードでLua Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Lua Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのLua Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。

「メタテーブル入門」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このLua Academyレッスンでコードを書いて実行できますか?

はい。すべてのLua Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. メタテーブル入門
  2. __indexと__newindex
  3. 算術メタメソッド
  4. __tostringと__len
← Lua Academyに戻る