🌕

__newindexを利用したテーブルへの値の追加

2022/12/04に公開

はじめに

この記事は、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