Luaのちょっと分かりにくいけど便利かもしれない機能

まず、多重代入ができます。

a, b = 1, 2

右辺が全て評価され、値リストが完成してから代入が行われるのでスワップコードを

x, y = y, x

のように書けます。


関数は複数の値を返せます。

function f()
  return 1, 2, 3
end

1, 2, 3の部分を見ると多重代入の右辺と同じですね。「値リスト」のような値を考えるとよさそうです。

a, b, c = f()


配列(キーが整数のテーブル)のコンストラクタを見ると

array = {1, 2, 3}

というわけで

array = {f()}

ということが可能です。ただし、最後の要素として書いた時のみ全要素が使われ、

array = {f(), 5}
array = {1, 5}

最後以外に書いた場合、最初の1つだけが使われることになるそうです。ついでに最後に書く場合で最初の1個だけを使いたい場合はカッコ()で括れということです。

array = {5, (f())}
array = {5, 1}
g(0, 1, 2, 3)
g(0, f())

関数の引数リストを見てください。ということで下の書き方ができます。ただしテーブルコンストラクタの時と同じように最後以外は最初の要素のみが使われるそうです。


可変長引数が使えます。...は文法上意味のあるシンボルです。

function h(...)
end

...は種類としては1, 2, 3といった値リストとして見るといいようです。

a, b, c = ...

まあ普通は長さも調べたいし配列(テーブル)に変換するでしょう。

args = {...}


値リスト→テーブル は

array = {1, 2}

{}で括ればできるのですが、
テーブル→値リスト は

x, y = unpack(array)
x, y = 1, 2

unpack関数を使うとできます。unpackはオプショナルの引数unpack(array[, i[, j]])でarray[i]〜array[j]の範囲を取り出すことができます。


で、どうしてこれを書いたかというと(ここまで長いなおい)、シューティングを書くにあたって座標(位置ベクトル)にも速度にも加速度にもとにかくそこらじゅうに2次元ベクトルが不可欠となるわけです。これをLuaでどう書くかなんですが、

-- pos.x, pos.y
-- pos["x"], pos["y"]
pos = {x = 0, y = 0}

-- pos[1], pos[2]
pos = {0, 0}

どう見ても上の方が見やすくてたまらないのですが、見た目がCの構造体なだけで、中身はコメント2行目のように文字列をキーにした連想配列になります。一方、下の方は一応数値をキーにした連想配列なのですが、Luaはキーにはどんな値も使えるとしているものの、それぞれに応じた最適化を行います。言ってしまえばキーが数値(多分特に整数)の場合、Cレベルでの配列(メモリ上に連続でならんでおり、任意のkに対してa[k]へのアクセスがO(1))になります(と言っても今の私では想像の域でしかない、けどほぼ確実。)。文字列のキーは多分ハッシュテーブルによります。

void lua_createtable (lua_State *L, int narr, int nrec);

新しい空のテーブルを作り、スタックに積む。この新しいテーブルは narr 個の配列要素と nrec 個の非配列要素のための割り当て済み空間を持っている。この事前割り当ては、そのテーブルがたくさんの要素を持つであろうとわかっている場合に役に立つ。それ以外の場合は、関数 lua_newtable を使っても良い。

これはC APIの新しいテーブルを作ってスタックに積む関数です。スクリプト言語らしく、配列の長さは自動的に伸びますし、新たな文字列キーが来たらそれに対応します。しかしこれはCレベルで見ればメモリの(再)確保を必要とする重い処理です。でも、最初に大量に用意してしまうとあまり使わなかったときに無駄になって非常に残念です。で、使う量があらかじめ(だいたいでも)分かっていればヒントとして与えることで効率化ができます。可変長配列やハッシュテーブルのライブラリによくある実装です。リファレンスマニュアルには内部実装についてはほぼ全く書かれていませんが、こういうところから読みとらなければなりません。「非配列要素の数」nrecを受理することから二分木ではなくハッシュだろうというところまで想像がつきます。
まあこんなことは最初から分かっててですね、"x","y"だけのハッシュテーブルなんて作りたくないです、ってことです。結構実際に作成される数も多くなりそうだし。いやまあテーブル1つに少なくとも配列・非配列それぞれについての何らかのデータがあるんで真に必要なデータx,yに対するメモリのオーバーヘッドはどうしても大きくなるだろうけど。ライトユーザーデータ使えってか。で、配列で軽くライブラリを書いたのですが、C++からエクスポートした関数(draw_imageとか)に渡すときに

dg.draw_image_by_center(
  self.gt.player,
  self.player.pos[0],
  self.player.pos[1])

と、死にたくなるような状態に。しかし頭を柔軟にして次のコードを考えつく。

dg.draw_image_by_center(
  self.gt.player, unpack(self.player.pos))

しかしリファレンスをよく読むとこれの後に引数があった場合最初の1つのみが使われるというさっきのアレで無理ゲーに(実際第4引数は省略可能の回転角度)。で、ついに出てきた多分最良の書き方がこれ。

local x, y = unpack(self.player.pos)
dg.draw_image_by_center(self.gt.player, x, y)

これはいいな。これはいい…が、これを軽く思いつく頭にはなっていないようだ…。