luaでオブジェクト指向

# luaでオブジェクト指向

この記事は[アドカレ](https://qiita.com/advent-calendar/2023/hachinohe_kosen)に参加しています。

:::note warn
この記事で紹介しているのはlua5.1についてです。
:::


# オブジェクト指向
　オブジェクト指向という考え方の根幹は、「楽をしたい」です。

 例えば、以下のようなコードを見てみましょう。
 ```lua
local a1,a2=0,0
local b1,b2=1,2

local c1=a1+b1
local c2=a2+b2
 ```
`a`と`b`で足し算をしていますね。`1`とか`2`とかでこんがらがってしまいます。足し算の式も二つもあって分かりづらいです。

行列で書くと、
```math
\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でこのような楽をするにはメタテーブルというものを使用します。自分で予めオブジェクト同士の演算方法を定義しておくことで、上記のプログラムは次のように書き直すことができます。
```lua
local A=new_vec(0,0)
local B=new_vec(1,2)

local C=A+B
```
一回の足し算で済むので楽でいいですね、

# メタテーブルの基本
　luaでいうメタテーブルというのは変数やオブジェクトの持つ特性のことです。`+`や`-`といった記号があった際にどんな演算をするのか、`==`のような比較演算子がある際にはどのように比較するかなど、メタテーブルは変数やオブジェクトの振る舞いを決めます。

# メタテーブルのプログラム
　メタテーブルのプログラムについてみていきましょう。

## メタテーブルの最低限
　自分で新しいメタテーブルを作成するには、まずメタテーブルを表すオブジェクトを一つ用意します。
 ```lua
local vec={}
 ```
　次に、`メタテーブル名.__index`という名前で、定義したメタテーブルを持つオブジェクトが呼び出されたときに返す値を決めます。
 ```lua
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`関数を使用します。第一引数にオブジェクトを、第二引数にメタテーブルの名前を指定することで、返り値としてメタテーブルを持ったオブジェクトがきます。
```lua
local a={0,0}
local b=setmetatable(a,vec)
```
　`setmetatable`のラッパーとして以下のような関数を定義しておくと、簡単にメタテーブルを持ったオブジェクトを作成することができます。
 ```lua
local function new_vec(x,y)
  local a={x,y}
  return setmetatable(a,vec)
end
 ```

## 演算の定義
　メタテーブルを持ったオブジェクトの演算を定義することができます。定義することのできる演算一覧は[こちら](https://www.lua.org/manual/5.1/manual.html#2.8)を確認してください。

以下にメタテーブル`vec`での足し算の定義の仕方を示します。`vec.__add`の引数は二つありますが、片方はメタテーブル`vec`を持っていない可能性があるので`tcheck`でメタテーブル`vec`を持っているのかを確認し、メタテーブルを持っていない場合には新しいオブジェクトとして定義しておきます。
```lua
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`でそれぞれ四則演算をするようなメタテーブルを以下に示します。
```lua
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
```
　　　　　　
使用例です。
```lua
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](https://www.lua.org/manual/5.1/manual.html#2.8)
[第 4 回: Lua のオブジェクト指向について紹介する](https://ie.u-ryukyu.ac.jp/~e085739/lua.hajime.4.html)
[luaのメソッドで ":" を避ける(lua scriptとc++両方)](https://qiita.com/ousttrue/items/d11f607c89b86707d751)

# むすび
　メタテーブルを使用したプログラムというのは、メタテーブルを使用してないプログラムよりも遅いらしいです。僕はluaを即時実行できるスクリプト言語として用いることが多いので、c++互換になるような書き方ができるだけでも嬉しいんですけどね。