0Pricing
Lua Academy · レッスン

__indexと__newindex

__indexと__newindexのメタメソッドでフィールドの読み書きを横取りします。

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

__indexのまとめ

__indexは、Luaがテーブルに存在しないキーを読み取ろうとしたときに呼び出されます。テーブル(プロトタイプ検索)にも、関数(動的な計算)にもできます。これはLuaで最もよく使われるメタメソッドで、継承、デフォルト値、遅延初期化を実現します。

local defaults = {timeout=30, retries=3}
local config = setmetatable({timeout=60}, {__index=defaults})

print(config.timeout)   -- 60 (own value)
print(config.retries)   -- 3  (from defaults)
print(config.missing)   -- nil (not in either)

__newindexが呼び出される場合

__newindexは、Luaがテーブルにまだ存在しないキーを書き込もうとしたときに呼び出されます。キーがすでに存在する場合、代入は__newindexを呼び出さずに直接行われます。

local proxy = setmetatable({}, {
  __newindex = function(t, k, v)
    print("New key:", k, "=", v)
    rawset(t, k, v)  -- actually store it
  end
})

proxy.x = 10    -- New key: x = 10
proxy.x = 20    -- no trigger! x already exists
print(proxy.x)  -- 20

読み取り専用テーブル

__newindexを使うと、読み取り専用テーブルを作成できます。すべての書き込みを横取りしてエラーを発生させます。__indexと組み合わせると、テーブル自体に値を保存せずに値を提供できます。

local function readOnly(t)
  return setmetatable({}, {
    __index = t,
    __newindex = function(_, k, _)
      error("attempt to write to read-only table key: " .. tostring(k), 2)
    end
  })
end

local cfg = readOnly({host="localhost", port=8080})
print(cfg.host)    -- localhost
-- cfg.host = "x"  -- ERROR: attempt to write to read-only table

変更の追跡

__newindexを使うと、テーブルへのすべての変更を追跡できます。デバッグ、変更ログの記録、リアクティブなデータバインディングに役立ちます。データは非公開の「バッキング」テーブルに保存し、変更をログに記録します。

local log = {}
local data = {}
local tracked = setmetatable({}, {
  __index = data,
  __newindex = function(_, k, v)
    log[#log+1] = {key=k, old=data[k], new=v}
    data[k] = v
  end
})

tracked.name = "Alice"
tracked.age = 30
tracked.name = "Bob"

for _, entry in ipairs(log) do
  print(entry.key, entry.old, "->", entry.new)
end

プロキシオブジェクト

プロキシは別のオブジェクトをラップし、すべての読み取りと書き込みを横取りします。__indexと__newindexの両方を関数に指定した空のテーブルをプロキシとして使用します。実際のデータは別のテーブルに保存します。

local function makeProxy(target)
  return setmetatable({}, {
    __index = function(_, k) return target[k] end,
    __newindex = function(_, k, v)
      print("Setting", k, "on target")
      target[k] = v
    end,
  })
end

local real = {x=10}
local p = makeProxy(real)
print(p.x)   -- 10 (reads from real)
p.y = 20     -- Setting y on target
print(real.y) -- 20

__indexチェーン(継承)

__indexメタテーブルを連鎖させると、プロトタイプ継承を作成できます。オブジェクトでキーが見つからない場合、Luaはクラス、親クラスという順に確認します。各レベルには、__indexが次のレベルを指す独自のメタテーブルが必要です。

local Base  = {type = "base",  version = 1}
local Child = setmetatable({type = "child"}, {__index = Base})
local Obj   = setmetatable({}, {__index = Child})

print(Obj.type)     -- base? No: child (Child has it)
print(Obj.version)  -- 1 (from Base)
print(Obj.type)     -- child (Child overrides Base.type)

__indexによる遅延初期化

関数形式の__indexを使うと、コストの高い値を初回アクセス時に計算してキャッシュできます。最初の読み取りで値を計算し、テーブルに直接保存します(以降の__index呼び出しを回避できます)。

local lazy = setmetatable({}, {
  __index = function(t, k)
    if k == "expensiveData" then
      print("Computing...")
      local result = {1,2,3,4,5}   -- simulate work
      rawset(t, k, result)
      return result
    end
  end
})

print(lazy.expensiveData)  -- Computing...  then table
print(lazy.expensiveData)  -- (no "Computing" this time)

スキーマ検証のための__newindex

__newindexを使ってスキーマを適用し、あらかじめ定義したキーや正しい型の値だけを設定できるようにします。これにより、Luaのテーブルに軽量な型安全性を加えられます。

local schema = {name="string", age="number", active="boolean"}

local function schemaTable()
  local store = {}
  return setmetatable({}, {
    __index = store,
    __newindex = function(_, k, v)
      local expected = schema[k]
      if not expected then error("unknown key: "..k,2) end
      if type(v) ~= expected then
        error(k.." must be "..expected..", got "..type(v),2)
      end
      store[k] = v
    end
  })
end

local user = schemaTable()
user.name = "Alice"
user.age  = 30
-- user.age = "thirty"  -- ERROR

__indexとrawgetの違い

__index関数の内部では、__indexを再び呼び出さずにテーブルから読み取るため、rawgetを使用してください(無限再帰を引き起こすためです)。メタメソッド内で実際のテーブルの保存領域を操作するときは、必ずrawget/rawsetを使用してください。

local counter = setmetatable({count=0}, {
  __index = function(t, k)
    -- Use rawget to avoid recursion
    local c = rawget(t, "count")
    rawset(t, "count", c + 1)
    return rawget(t, k)
  end
})

print(counter.count)    -- 0 (direct, no __index)
print(counter.missing)  -- nil
print(counter.count)    -- 1 (incremented)

__indexと__newindexの組み合わせ

最も強力なパターンは、__indexと__newindexの両方を備えた空のプロキシテーブルを、非公開のデータテーブルで支える方法です。これにより読み取りと書き込みを完全に制御でき、変更検知、遅延読み込み、アクセス制御などの機能を実現できます。

local function observable(init)
  local data = init or {}
  local listeners = {}
  local obj = setmetatable({}, {
    __index = data,
    __newindex = function(_, k, v)
      local old = data[k]
      data[k] = v
      for _, cb in ipairs(listeners) do cb(k,old,v) end
    end
  })
  obj._onchange = function(_, cb) listeners[#listeners+1]=cb end
  return obj
end

local obs = observable({score=0})
obs:_onchange(function(k,o,n) print(k,o,"->",n) end)
obs.score = 100  -- score  0 -> 100

確認問題

__newindexが呼び出されないのはどのような場合ですか?

まとめ:__indexと__newindex

重要なポイント:

  • __index:存在しないキーの読み取り時に呼び出されます。テーブルまたは関数を指定できます
  • __newindex:新しいキーへの書き込み時だけ呼び出されます
  • 再帰を避けるため、メタメソッド内ではrawget/rawsetを使用します
  • パターン:読み取り専用テーブル、変更追跡、遅延初期化、スキーマ検証
  • 空のプロキシとバッキングテーブルの組み合わせで、読み取りと書き込みを完全に横取りできます

よくある質問

「__indexと__newindex」レッスンは無料ですか?

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

「__indexと__newindex」で何を学びますか?

__indexと__newindexのメタメソッドでフィールドの読み書きを横取りします。 ブラウザで直接実行するハンズオンコードでLua Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

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

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

「__indexと__newindex」レッスンにはどのくらい時間がかかりますか?

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

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

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

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

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