LuaでOOP


Luaって速くていいよって声が聞こえてきたので、Luaをちょっと調べてみる。本格的なプログラムを書くなら、やっぱりオブジェクトくらい作れないとだめだよね、ってことでLuaOOP



Lua自体はオブジェクト指向ではないけど、テーブルを使ったプロトタイプオブジェクト指向っぽいことができる。
ここで紹介するのは、僕の考えた一例。
もっといい方法を考えたら教えてね。

基礎知識


どんな仕組みで実現しているかを理解するための基礎知識。使うだけなら必要ないかも

テーブル



他の言語では連想配列とか辞書と呼ばれるもの。
C言語の配列は添字に整数値しか使えないけど、Luaのテーブルは文字列も指定できる。

table = {}
table[1] = "one"
table["two"] = 2


テーブルへのアクセスにはシンタックスシュガーが用意されており、
var["name"]とvar.nameは同じ意味です。

x={a=1, b=2, c=3}
print(x.a)


この書き方を使うと、テーブルをオブジェクトのように扱うことができるわけです。

関数



みんな知っていると思うので、関数の概念自体は説明略。
関数定義にはいくつかシンタックスシュガーが用意されています。
関数定義をちゃんと書くと以下のようになります。

関数名 = function(引数)
...処理...
end

これを次のように簡単に書ける。

function 関数名(引数)
...処理...
end

このことを知っていれば次のようなコードも理解できますね。

list = {}
function list.method()
...処理...
end

さらにコロン(:)を使った、こんなシンタックスシュガーも用意されています。
以下の二つの関数定義は同じ意味です。

function a:b() end
function a.b(self) end

呼び出しも同じようにコロンを使うと簡単に書けます。

a:b()
a.b(a)

この表記方法を使うと、メソッドっぽいものが作れます。

メタテーブル



演算子のオーバーライドを実現するための仕組み。
Luaが扱う値はすべてメタテーブルをもつことができ、setmetatableという組み込み関数で書き換えることが可能です。

    • テーブルを作成

a = {x=1, y=2}
b = {x=3, y=5}

    • メタテーブルの設定

setmetatable(a, {
__add = function(l, r) -- +演算子を上書き
return {x=l.x+r.x, y=l.y+r.y}
end
})

c = a+b
print(c.x, c.y)


リスト同士の+演算子は定義されていませんが、メタテーブルを設定することで意味が変更されたことがわかります。
なんだかオブジェクト指向っぽくなってきました。



ちなみに再定義可能な演算子は以下のとおり。


ここで__indexと__newindexは、テーブル内に指定されたキーが存在するときには、実行されないので注意。
また、__indexと__newindexにはテーブルを指定することもでき、その場合はそのテーブルに対して要素の参照や追加が行われます。

クラスを作ってみる



ここまでの基礎知識を使えば、こんなふうに書けます。
例として、二次元ベクトル「Vector」の定義

Vector = {} -- クラスメソッドを格納するためのテーブル
function Vector:new(x,y) -- コンストラクタ
local obj={x=x, y=y} -- オブジェクトを作成
return setmetatable(obj, {__index=self}) -- 作成したオブジェクトとクラスVectorを関連付ける
end

function Vector:magnitude() --メンバー関数
return math.sqrt(self.x^2 + self.y^2)
end

vec = Vector:new(1,1) -- 新規オブジェクトの作成
print(vec:magnitude()) -- メンバー関数の呼び出し


Vector.newがコンストラクタです。オブジェクトを作成して返します。
オブジェクトを返す前に、メタテーブルの__indexにselfを指定することで、関連付けを行ないます。
Vector:newの形式で呼べばself=Vectorなので結局Vectorと関連付けされます。


vecにはmagnitudeというメンバはありません。
このような場合、メタテーブルの__indexに指定してあるテーブルを探しに行きます。
Vectorにはmagnitudeがあるので、結果、Vector:magnitudeが呼び出されます。

クラスの継承



メタテーブルの__indexに指定してあるテーブルに指定されたキーがなければ、
さらにそのテーブルの__indexを探しに行きます。
これを利用すれば継承が可能。

VectorEx = {}
setmetatable(VectorEx, {__index=Vector}) -- Vectorを継承
function VectorEx:new(x,y,z)
local obj = Vector.new(self,x,y)
obj.z = z
return obj
end

function Vector:magnitude() --メンバー関数
return math.sqrt(self.x^2 + self.y^2 + self.z^2)
end

vec = VectorEx:new(1,1,1)
print(vec:magnitude())


参考文献