うたカモ技術ブログ

Lua

Luaプログラミング   文法まとめ その2(モジュール・メタテーブル)

post:     update: 

この記事では、モジュールとメタテーブルについて紹介します。

Luaのモジュールは、C言語で利用されるヘッダーファイルなどのライブラリに相当します。一方、メタテーブルとは 自身に対して演算子が使用された場合に、予め登録した任意の処理を実行するように設定したテーブルのことです。

まとめ1の内容と合わせて、この記事で紹介するモジュールとメタテーブルの使い方を理解すれば、一通りの処理を記述することができるようになります。

それでは、行ってみましょう。

※この記事は以下の環境で実施した結果を元に作成しています。

#実行環境OS
ubuntu 22.04 LTS 64bit
#Lua
Ver 5.4.4

目次

  1. モジュール
  2. メタテーブル

モジュール

モジュールの定義方法と呼び出しについて説明します。

[モジュール定義]
通常、Luaスクリプトは関連する処理のまとまりを「モジュール」と呼ばれる単位で管理します。 このモジュールを他のLuaスクリプトがインポートすることで、その中で収録されている関数を使用できるようになります。

モジュールは関数定義の集合であり、関数定義をテーブルとして外部へ提供するものです。 例として、greet関数とadd関数を管理するモジュール(mod.lua)を掲載します。

-- mod.lua
local greet = function(name)
    local user_name = tostring(name) or "unname"
    print("Hello " .. user_name .. "!")
end

local add = function(x, y)
    local x_val = tonumber(x) or 0
    local y_val = tonumber(y) or 0

    return x_val + y_val
end

--外部公開したい関数をテーブルメンバーに登録してreturnします。
return {
	greet = greet,
	add = add,
}

見た目は通常のLuaスクリプト内の関数定義と変わりません。 ただし、スクリプトの最後に無名テーブルを作成し、そのメンバーとして上記の関数名を指定したものを returnで返す必要があります。

Luaで管理されるモジュールはテーブルとして表現されます。今回の例では、外部のLuaスクリプトに公開する関数を 無名テーブルのメンバー変数として登録しています。もし、このモジュールの内部だけで使用される関数が存在し、それを 外部へ公開したくないといった場合には、その関数をこの無名テーブルのメンバ変数に代入しなければ良いだけです。

これによって、モジュールが持つ関数の外部参照の許可・禁止が実現できます。

補足:関数情報を含むテーブルを持たせる関係上、モジュール定義の仕方は設計者によって若干の癖が出る場合があります。 ここでは、無名テーブルを作成していますが、modテーブルを明示して作成し、そのメンバーとして関数定義を指し示す方法もあります。 他の技術ブログで紹介されているモジュール定義の仕方と見比べてみると良いでしょう。

[モジュールインポート]
reuqire関数を使用することで任意のモジュールをインポートできます。上記で説明したように、モジュールは テーブルとして取得されますので、次の例では変数modのデータとしてモジュールを表すテーブルを取得しています。

-- myscript.lua
local mod = require("mod")
mod.greet('utakamo')
print('add(10,20) = ' .. mod.add(10,20))

後は、modテーブルを通して関数を呼び出せます。上記の実行結果は次の通りです。

kamo@kamo:~ $ ls
myscript.lua   mod.lua    <---同じディレクトリに今回作成したスクリプトを置いています。
kamo@kamo:~ $ lua myscript.lua
Hello utakamo!
add(10,20) = 20

メタテーブル

テーブルを対象とした演算操作等が行われた際に、ユーザー独自の関数を実行させることができます。 これらの関数はメタメソッドと呼ばれ、それらを管理するテーブルをメタテーブルと呼びます。

メタメソッドの名前は予約されており、例を挙げると以下のものがあります。

No. メタメソッド名 呼び出し条件
1 __index テーブルに存在しないキーアクセスが発生したとき
1 __add テーブルをオペランドとして加算したとき
2 __sub テーブルをオペランドとして減算したとき
3 __mul テーブルをオペランドとして乗算したとき
4 __div テーブルをオペランドとして除算したとき

通常、メタテーブルにキーアクセスや加減剰余に対するカスタム関数定義をメタメソッドとして 登録し、それを適用したいテーブルと紐づけます。メタテーブルとテーブル間の紐づけはsetmetatable関数で実施します。

次は、キーアクセスと加減剰余に対するカスタム関数定義を追加したメタテーブルmetaTableを作成し、 coffeeTableに対してsetmetatable関数で紐づけしてみた例です。

-- test_metatable.lua
local coffeeTable = {
    beans = 'bluemountain',
    smell = 'excellent',
    taste = 'excellent'
}

--メタテーブルを作成(以下、__xxxの形式でメタメソッドを定義)
local metaTable = {
        -- テーブルのキーが存在しない場合は呼び出されます。
        __index = function(table, key)
                return '存在しないキーでアクセスしました。'
        end,

        -- 以下は加減剰余のメタメソッドです。
        -- 2つの引数のどちらかが数値でないと呼び出されます。
        -- [加算]
        __add = function(a, b)
                return 'テーブルをオペランドとした加算が実行されました。(' .. type(a) .. ',' .. type(b) .. ')'
        end,

        -- [減算]
        __sub = function(a, b)
                return 'テーブルをオペランドとした減算が実行されました。(' .. type(a) .. ',' .. type(b) .. ')'
        end,

        -- [乗算]
        __mul = function(a, b)
                return 'テーブルをオペランドとした乗算が実行されました。(' .. type(a) .. ',' ..type(b) .. ')'
        end,

        -- [除算]
        __div = function(a, b)
                return 'テーブルをオペランドとした除算が実行されました。(' .. type(a) .. ',' .. type(b) .. ')'
        end
}

--テーブルとメタテーブルを関連付ける
coffeeTable = setmetatable(coffeeTable, metaTable)

print(coffeeTable.beans)
print(coffeeTable.bitter)       -- __indexメソッドが呼び出されます。

print(coffeeTable + 100)        -- __addメソッドが呼び出されます
print(coffeeTable - 200)        -- __subメソッドが呼び出されます
print(coffeeTable * 300)        -- __mulメソッドが呼び出されます
print(coffeeTable / 400)        -- __divメソッドが呼び出されます。

実行結果は以下のようになります。

kamo@kamo:~ $ lua test_metatable.lua
bluemountain
存在しないキーでアクセスしました。
テーブルをオペランドとした加算が実行されました。(table,number)
テーブルをオペランドとした減算が実行されました。(table,number)
テーブルをオペランドとした乗算が実行されました。(table,number)
テーブルをオペランドとした除算が実行されました。(table,number)

参考文献