1. ホーム
  2. lua

[解決済み] Lua 5.1の__callメタメソッドは、実際にはどのように動作するのでしょうか?

2022-02-09 07:35:22

質問

練習として、Luaでセットの実装を作ろうとしています。 具体的には、Pil2 11.5の単純化されたセット実装を、値の挿入、値の削除などの機能を含むように成長させたいと考えています。

さて、これを行うための明白な方法(そして実際に機能する方法)はこうです。

Set = {}
function Set.new(l)
    local s = {}
    for _, v in ipairs(l) do
        s[v] = true
    end
    return s
end
function Set.insert(s, v)
    s[v] = true
end

ts = Set.new {1,2,3,4,5}
Set.insert(ts, 5)
Set.insert(ts, 6)

for k in pairs(ts) do
    print(k)
end

予想通り、1から6までの数字がプリントアウトされました。 しかし、これらの Set.insert(s, value) は本当に醜いです。 むしろ、次のようなものを呼び出せるようにしたいものです。 ts:insert(value) .

これを解決するために私が最初に試みたのは、次のようなものでした。

Set = {}
function Set.new(l)
    local s = {
        insert = function(t, v)
            t[v] = true
        end
    }
    for _, v in ipairs(l) do
        s[v] = true
    end
    return s
end

ts = Set.new {1,2,3,4,5}
ts:insert(5)
ts:insert(6)

for k in pairs(ts) do
    print(k)
end

これは、何が出てくるか見るまでは、ほとんど問題なく動作します。

1
2
3
4
5
6
insert

非常に明らかに、セットテーブルのメンバーであるinsert関数が表示されています。 これは、元の Set.insert(s, v) という問題が発生しやすくなります(例えば、誰かが入力しようとしている有効なキーが "insert" だった場合、どうなるでしょうか)。 もう一度、本を読んでみましょう。 代わりにこれを試すとどうなるのでしょうか?

Set = {}
function Set.new(l)
    local s = {}
    setmetatable(s, {__call = Set.call})
    for _, v in ipairs(l) do
        s[v] = true
    end
    return s
end
function Set.call(f)
    return Set[f]
end
function Set.insert(t, v)
    t[v] = true
end

ts = Set.new {1,2,3,4,5}
ts:insert(5)
ts:insert(6)

for k in pairs(ts) do
    print(k)
end

今、私がこのコードを読んでいる方法は

  • を呼び出すと ts:insert(5) というのは insert が呼び出されるために存在しないことを意味します。 ts metatable が検索されることになります。 "__call" .
  • ts メタフェースの "__call" キーが返す Set.call .
  • 現在 Set.call という名前で呼び出されます。 insert を返すようになります。 Set.insert 関数を使用します。
  • Set.insert(ts, 5) が呼び出されます。

実際に起きていることはこうだ。

lua: xasm.lua:26: attempt to call method 'insert' (a nil value)
stack traceback:
        xasm.lua:26: in main chunk
        [C]: ?

そして、この時点で私は行き詰ってしまったのです。 これからどうすればいいのか全く分からないのです。 このコードに様々な程度の絶望的なバリエーションを加えて1時間ハックし続けましたが、最終的には何も動作しないのです。 私はこの時点でどのような間違いなく明白なことを見落としているのでしょうか?

解決するには?

<ブロッククオート

今、私がこのコードを読む方法は。

  • ts:insert(5)を呼び出すと、呼び出されるinsertが存在しないということは、ts metatableが"__call"で検索されることになりますね。

そこにあなたの問題がある。 その __call メタメソッドが参照されるのは テーブル自体 が呼び出されます(つまり、関数として)。

local ts = {}
local mt = {}

function mt.__call(...)
    print("Table called!", ...)
end

setmetatable(ts, mt)

ts() --> prints "Table called!"
ts(5) --> prints "Table called!" and 5
ts"String construct-call" --> prints "Table called!" and "String construct-call"

Luaのオブジェクト指向のコロンコールはこんな感じです。

ts:insert(5)

の単なる構文上の糖分です。

ts.insert(ts,5)

の構文糖である。

ts["insert"](ts,5)

そのため、このアクションは ts コール でなく インデックス (その 結果 ts["insert"] と呼ばれるもの)に支配されている。 __index メタメソッドです。

__index メタメソッドには、インデックスを別のテーブルにフォールバックさせたい場合のテーブルを指定することができます (ただし、これは の値は、__indexキー は、インデックスを作成するメタテーブルと ない を使用します(メタ情報自体)。

local fallback = {example = 5}
local mt = {__index = fallback}
local ts = setmetatable({}, mt)
print(ts.example) --> prints 5

__index メタメソッドを関数として使用する場合は、 Set.call で指定したシグネチャと同じように動作しますが、 キーの前にインデックスを作成するテーブルが渡されます。

local ff = {}
local mt = {}

function ff.example(...)
  print("Example called!",...)
end

function mt.__index(s,k)
  print("Indexing table named:", s.name)
  return ff[k]
end

local ts = {name = "Bob"}
setmetatable(ts, mt)
ts.example(5) --> prints "Indexing table named:" and "Bob",
              --> then on the next line "Example called!" and 5

メタテーブルの詳細については マニュアル .