LuaでOOP
Luaって速くていいよって声が聞こえてきたので、Luaをちょっと調べてみる。本格的なプログラムを書くなら、やっぱりオブジェクトくらい作れないとだめだよね、ってことでLuaでOOP。
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)
リスト同士の+演算子は定義されていませんが、メタテーブルを設定することで意味が変更されたことがわかります。
なんだかオブジェクト指向っぽくなってきました。
ちなみに再定義可能な演算子は以下のとおり。
- "__add" 二項演算子 +
- "__sub" 二項演算子 -
- "__mul" 二項演算子 *
- "__div" 二項演算子 /
- "__mod" 二項演算子 %
- "__pow" 二項演算子 ^
- "__concat" 二項演算子 ..
- "__unm" 単項演算子 -
- "__len" 単項演算子 #
- "__eq" 比較演算子 ==
- "__lt" 比較演算子 <
- "__le" 比較演算子 <=
- "__index" テーブル内の要素を参照
- "__newindex" テーブルへの要素の追加
- "__call" 関数呼び出し
- "__gc" ガベージコレクタによって破棄されるとき(C言語との連携時に使う)
- "__tostring" tostring() 関数を用いて、値を文字列に変換する時に呼ばれる
- "__mode" 弱参照テーブルを作成する
ここで__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())