__newindexを利用したテーブルへの値の追加
はじめに
この記事は、Lua Advent Calendar 2022に投稿したものとなります。
前日までの記事は、リンクを参照してください。
この記事を書いている時点での筆者のLuaのバージョンは、以下となります。
$ lua -v
> Lua 5.3.5 Copyright (C) 1994-2018 Lua.org, PUC-Rio
__newindex
について
アドベントカレンダー前回の記事でも紹介がありましたが、Luaにはmetatable
という便利な機能が存在します。
この機能を利用すると、算術演算(+,-,*,/など)や比較演算(<,>,==など)がテーブルに対して利用されたときの振る舞い、テーブルが関数として呼び出されたときの振る舞いなどをメタ的に実装することが可能となっています。
詳細は、リファレンスを参照ください。
(日本語版)
その中の一つにnewindex
というイベントがあります。これは、table[key] = value
のように、テーブルに対してインデックスを用いた代入を行う際に発生するイベントで、__newindex
という属性をメタテーブルに設定しておくことで振る舞い(メタメソッド)を定義することが出来ます。
local M = setmetatable({}, {
__newindex = function(self, key, value)
print(("key='%s', value='%s'が代入されました。"):format(key, value))
end,
})
M["hoge"] = "fuga"
-- key='hoge', value='fuga'が代入されました。
この機能を利用することで少し複雑な代入をスッキリ書けるようになります。
例えば、キーが違うが代入したい値が同じ場合、通常の代入ではループを使って一つずつ代入する以下のような方法が取られるでしょう。
local M = {}
local keys = { "hoge", "fuga", "foo", "baa" }
for _, k in ipairs(keys) do
M[k] = "piyopiyo"
end
for k, v in pairs(M) do
print(("key='%s', value='%s'"):format(k, v))
end
-- key='fuga', value='piyopiyo'
-- key='baa', value='piyopiyo'
-- key='foo', value='piyopiyo'
-- key='hoge', value='piyopiyo'
これは、__newindex
を使って以下のように書き換えられます。
local M = setmetatable({}, {
__newindex = function(self, keys, value)
for _, k in ipairs(keys) do
rawset(self, k, value)
end
end,
})
M[{ "hoge", "fuga", "foo", "baa" }] = "piyopiyo"
for k, v in pairs(M) do
print(("key='%s', value='%s'"):format(k, v))
end
-- key='baa', value='piyopiyo'
-- key='fuga', value='piyopiyo'
-- key='foo', value='piyopiyo'
-- key='hoge', value='piyopiyo'
このように、__newindex
を利用することでテーブルをキーにして一括で代入するようなプログラムを書くことができるようになります。
注意点としては、__newindex
に定義したメタメソッドの中で、対象のテーブルに値を代入する際には、rawset
というLuaの関数を使うようにしてください。
このrawset
関数はテーブルに値を代入する際に、メタテーブル(メタメソッド)を利用しない操作をする関数です。この関数を利用しないと__newindex
のメタメソッドが無限に呼び出され続けてしまいエラーとなってしまいます。
また、ソート順が気になるところですが、こちらもtable.sort
というLuaの関数を利用することで操作可能です。比較演算子<
利用時のイベント__lt
のメタメソッドを設定しておくことでスッキリ書けるでしょう。
使用例
イマイチ使いどころが分からないかもしれないので、使用例を一つ載せておきます。
筆者はNeovimの設定をLuaで書いており、そこから抜粋した内容となります。
local langs = setmetable({}, {
__newindex = function(self, ftypes, settings)
-- 代入時の処理
end
})
-- for 'lua'
langs["lua"] = {
tabstop = 2,
shiftwidth = 2
}
-- for 'c' or 'cpp'
langs[{ "c", "cpp" }] = {
tabstop = 4,
configure = function(_)
setlocal.complete:append("i")
end,
}
(Neo)vimはファイルタイプ(概ねプログラミング言語ないしは、ファイルの種類)ごとの設定を書けるのですが、__newindex
を利用することで、共通した設定の言語はテーブルを利用して一括代入できるようにしてあります。
まとめ
__newindex
を利用することでテーブルに値を代入した際の振る舞いを定義することが出来ます。
メタテーブルを設定しておくことで、そのテーブルを利用するコード側をスッキリ書くことができるようになるのではないかと思っています。
Discussion