luaでオブジェクト指向
この記事はアドカレに参加しています。
この記事で紹介しているのはlua5.1についてです。
オブジェクト指向
オブジェクト指向という考え方の根幹は、「楽をしたい」です。
例えば、以下のようなコードを見てみましょう。
local a1,a2=0,0
local b1,b2=1,2
local c1=a1+b1
local c2=a2+b2
a
とb
で足し算をしていますね。1
とか2
とかでこんがらがってしまいます。足し算の式も二つもあって分かりづらいです。
行列で書くと、
\begin{align}
\begin{bmatrix} c1 \\ c2 \end{bmatrix}
&=
\begin{bmatrix} a1 \\ a2 \end{bmatrix}
+
\begin{bmatrix} b1 \\ b2 \end{bmatrix} \\
C&=A+B
\end{align}
のようになるので、できれば行列のように一回の足し算で終わらせたいです。
luaでこのような楽をするにはメタテーブルというものを使用します。自分で予めオブジェクト同士の演算方法を定義しておくことで、上記のプログラムは次のように書き直すことができます。
local A=new_vec(0,0)
local B=new_vec(1,2)
local C=A+B
一回の足し算で済むので楽でいいですね、
メタテーブルの基本
luaでいうメタテーブルというのは変数やオブジェクトの持つ特性のことです。+
や-
といった記号があった際にどんな演算をするのか、==
のような比較演算子がある際にはどのように比較するかなど、メタテーブルは変数やオブジェクトの振る舞いを決めます。
メタテーブルのプログラム
メタテーブルのプログラムについてみていきましょう。
メタテーブルの最低限
自分で新しいメタテーブルを作成するには、まずメタテーブルを表すオブジェクトを一つ用意します。
local vec={}
次に、メタテーブル名.__index
という名前で、定義したメタテーブルを持つオブジェクトが呼び出されたときに返す値を決めます。
function vec.__index(p,key) return key=="x" and p[1] or p[2] end
上記のコードの場合には、メタテーブルvec
を持つオブジェクトa
で、a.x
としたときにはa[1]
の値を返します。また、a.y
やa.sample
のようにx
以外の値で呼び出された際にはa[2]
を返します。
オブジェクトにメタテーブルを持たせる
オブジェクトにメタテーブルを持たせるにはsetmetatable
関数を使用します。第一引数にオブジェクトを、第二引数にメタテーブルの名前を指定することで、返り値としてメタテーブルを持ったオブジェクトがきます。
local a={0,0}
local b=setmetatable(a,vec)
setmetatable
のラッパーとして以下のような関数を定義しておくと、簡単にメタテーブルを持ったオブジェクトを作成することができます。
local function new_vec(x,y)
local a={x,y}
return setmetatable(a,vec)
end
演算の定義
メタテーブルを持ったオブジェクトの演算を定義することができます。定義することのできる演算一覧はこちらを確認してください。
以下にメタテーブルvec
での足し算の定義の仕方を示します。vec.__add
の引数は二つありますが、片方はメタテーブルvec
を持っていない可能性があるのでtcheck
でメタテーブルvec
を持っているのかを確認し、メタテーブルを持っていない場合には新しいオブジェクトとして定義しておきます。
local function tcheck(a)
return getmetatable(a)==vec and "vec" or type(a)
end
local function chengeVec(a)
return tcheck(a)=="vec" and a or new_vec(a,a)
end
function vec.__add(a,b)
a,b=chengeVec(a),chengeVec(b)
--xyそれぞれの要素で足し算する
return new_vec(a.x+b.x,a.y+b.y)
end
プログラム例
x
とy
でそれぞれ四則演算をするようなメタテーブルを以下に示します。
local vec={}
local function new_vec(x,y)
local a={x,y}
return setmetatable(a,vec)
end
local function tcheck(a) return getmetatable(a)==vec and "vec" or type(a) end
local function chengeVec(a) return tcheck(a)=="vec" and a or new_vec(a,a) end
function vec.__index(p,key) return key=="x" and p[1] or p[2] end
function vec.__add(a,b)
a,b=chengeVec(a),chengeVec(b)
return new_vec(a.x+b.x,a.y+b.y)
end
function vec.__sub(a,b)
a,b=chengeVec(a),chengeVec(b)
return new_vec(a.x-b.x,a.y-b.y)
end
function vec.__mul(a,b)
a,b=chengeVec(a),chengeVec(b)
return new_vec(a.x*b.x,a.y*b.y)
end
function vec.__div(a,b)
a,b=chengeVec(a),chengeVec(b)
return new_vec(a.x/b.x,a.y/b.y)
end
使用例です。
local x1,x2,d=new_vec(1,2),new_vec(3,4)
d=x1+x2
debug_print("test add : "..d.x.." : "..d.y)
--test add : 4 : 6
d=x1-x2
debug_print("test sub : "..d.x.." : "..d.y)
--test sub : -2 : -2
d=3*x2
debug_print("test mul : "..d.x.." : "..d.y)
--test mul : 9 : 12
参考文献
Lua 5.1 Reference Manual
第 4 回: Lua のオブジェクト指向について紹介する
luaのメソッドで ":" を避ける(lua scriptとc++両方)
むすび
メタテーブルを使用したプログラムというのは、メタテーブルを使用してないプログラムよりも遅いらしいです。僕はluaを即時実行できるスクリプト言語として用いることが多いので、c++互換になるような書き方ができるだけでも嬉しいんですけどね。