Luaでrefのようなものを作る


Luaでrefのようなものを作る

Luaで参照のようなものを作ります。テーブルに対する「参照値」ではなくプリミティブにも使える参照のような動作をするものです。

実装の方針

メタメソッドの __index および __newIndex によってテーブルへのアクセスを制御することができることを利用します。参照名と参照先 (テーブルとキーのペア) の対応を保持しローバルのrefオブジェクトへのアクセスにより参照先を操作します。

実装

refオブジェクト

refオブジェクトに次のメンバを持たせます。

キー
table "table" 参照名と参照先のリスト
put "function" 参照を追加するメソッド
delete "function" 参照を削除するメソッド

今回は簡単のためrefはグローバルオブジェクトとしますがインスタンスとする場合はメタテーブルから put メソッドと delete メソッドを呼べるようにすればよいです。

インデックスへのアクセス

refオブジェクトの __index を設定します。フィールドにアクセスされるとこの関数が呼ばれるため、 ref.table 内を参照して返します。ただし put メソッドや delete メソッドへのアクセスは特別に直接 ref オブジェクト内のメソッドを呼び出します。呼び出しが循環しないために rawget メソッドを使い ref 内のフィールドを直接参照します。なお、コード内ではメタテーブルに設定するためのオブジェクトを meta と表しています。

meta.__index = function(ref, key)
  if type(ref) ~= "table" then
    return nil
  end

  -- putフィールドとdeleteフィールドはref内を直接参照
  if key == "put" or key == "delete" then
    return rawget(ref, key);
  end

  -- ref.table内のフィールドを参照
  local obj = rawget(ref, "table")[key]
  if obj ~= nil then
    return (obj.table)[obj.key]
  end

  return nil
end

代入も同様にして行えます。

meta.__newindex = function(ref, key, value)
  if type(ref) ~= "table" then
    return nil
  end

  -- ref.table内のフィールドを参照
  local obj = rawget(ref, "table")[key]
  if obj ~= nil then
    (obj.table)[obj.key] = value
  end
end

put メソッドと delete メソッドは単純に ref.table 内を書き換えるだけです。メタテーブルの設定前に書くと rawget を使わずに済みます。

ref.put = function(name, table, key)
  rawget(ref, "table")[name] = {
    table = table,
    key = key
  }
end

ref.delete = function(name)
  rawget(ref, "table")[name] = nil
end

振る舞い

次のように参照のような使い方をできます。ただしテーブルへの参照はテーブルを参照する値への参照になります。

local table = {
  a = 0
}

ref.put("a", table, "a")
ref.put("a2", table, "a")

print(table.a .. ", " .. ref.a .. ", " .. ref.a2)    -- 0, 0, 0
table.a = 10
print(table.a .. ", " .. ref.a .. ", " .. ref.a2)    -- 10, 10, 10

ref.a = 20
print(table.a .. ", " .. ref.a .. ", " .. ref.a2)    -- 20, 20, 20

ref.a2 = 30
print(table.a .. ", " .. ref.a .. ", " .. ref.a2)    -- 30, 30, 30

また、次のように参照に対して参照を設定することもできます。この場合も結果は同様です。

ref.put("a2", ref, "a")