Lua は素晴らしい言語なのですが、現実の言語である以上、使っていくと色々と不満点が出てきます。
- local として宣言してない変数に触ると、暗黙のうちにグローバル変数として扱われる
- 多値を扱えるが、少しばかり残念なところもある
- 多値を変数に束縛する簡単な手段がない
- 多値をテーブルのキーとして使えない
- どうしても使いたい場合は、テーブルに入れた上で保存しておいて使い回すしかない
- 独立に作られたテーブルだと、もうお手上げ状態
- val 欲しい
エトセトラエトセトラ…。
で、今回はその中の一つ、多値の扱いについて、タプルが欲しいよねという話。
Python 分かる人は Python のタプルをイメージしてください。あんなのです。
具体的に、 Lua において僕が欲しいタプルの特徴を挙げると、
- 複数の値をまとめたもの
- 基本的には数値インデックスでアクセスする
- それ以外にも unpack や select があると良い
- 格納される値は immutable
- 再代入することが出来ないので安心して使いまわせる
- 再代入っぽいことをしたい場合は、要素を置き換えた新しいタプルを作る
- オブジェクトではなく値の論理で動く
- つまり比較するときは中身を比較する
- テーブルの場合は
{ 1, 2 } == { 1, 2 }
は false だけど、それじゃ嫌 - メタテーブルを使って == を書き換えたのでは対処できない場合も多い
- 具体的にはテーブルのキーとして使う場合
- テーブルの場合は
- つまり比較するときは中身を比較する
- 全体的に、文字列をイメージすると分かりやすい
といったところでしょうか。
で、これらの特徴を満たすものを 言語組み込みで欲しいな、と思うんですが、
Lua のコードをいじって 組み込みで新しくタプル型を作ることは、確かに可能ではあるものの、
僕にはそこまでの気力はないし、出来たとしても それは純正 Lua とは別物なのでアレです。
というわけで、ライブラリで実装してみることにしました:
http://gist.github.com/708819
使用コード例:
require "tuple" local t = tuple.new( 1, "a" ) print( t() ) -- 1 a print( t(1) ) -- 1 a print( t(2) ) -- a print( t(3) ) -- print( t('#') ) -- 要素数 2 assert( t == tuple.new( 1, "a" ) ) -- もう一度作っても同じ! local ls = {} ls[t] = "hoge" print( ls[tuple.new(1, "a")] ) -- hoge -- list 風に local t2 = tuple.cons( 1, tuple.cons( "a", tuple.nil_ ) ) assert( t == t2 ) assert( tuple.car(t) == 1 ) assert( tuple.cdr(t) == tuple.new("a") ) local nil_ = tuple.new() -- 空 tuple assert( nil_ == tuple.nil_ )
特徴としては、 Flyweight です。
つまり、同じ値の組から作られたタプルは、全て同じオブジェクトになります。
これにより、独立に作られたタプルでも、安心してテーブルのキーに使えるし、
数百個とか独立にタプルを作っても、実際のメモリ使用量は一個と変わりません。
また、クロージャベースなので、要素数が少ない時にはテーブルよりも高速に動作し、
中身の書き換えも原理的に起こりえないので、非常に安全。
もちろん、 Flyweight であっても、きっちり GC は動作し、
弱参照テーブルを多段に使ってるため、一度の GC サイクルでは全てのオブジェクトを回収できませんが、
繰り返し GC を実行することで、最終的には使われていない全てのタプルを回収できます。
一方で、 Flyweight にする為に、構築時のコストは大きくなっており、
また、これは完全に実装の都合上なのですが、長いタプルを使う場合、
要素数 N に対して Ο( N^2 ) のメモリ使用量を要求してしまいます。
これは勘違いでした。メモリ使用量は要素数 N に対し Ο(N) です。
ただ、普通に共有させた場合に比べ、メモリ効率があまりよくないのは事実。
なので、実用する場合は、せいぜい十数要素程度に押さえておき、
どうしても要素数が増えそうな場合には、タプルのタプルを作るなど、工夫が必要になりそうです。
と、このように問題点もありますが、
テーブルのキーとして自然に使えるタプルは、あると便利なのは間違いないので、
もし気に入ったなら、適当に改良しつつ、使ってみるといいんじゃないでしょうか。
// というかこれ、インターフェイス的にはどう見てもリスト…。
// リストと違って大量のデータは格納できないけどな!