Lua で定数
スクリプト言語の比較が ja.reddit で紹介されていたのでひさびさに見る……あ、そういえば reddit の URL 設計が変わりましたねぇ。新しい方がいいと思いますけど、既存のように ja.reddit.com にアクセスしたらふつうの reddit.com が出てきたのはちょいと驚いたぞ。
で、まあ、件の比較ページは昔っからあるヤツだし、ビミョウにちょっと口を挟みたくなるようなそうでもないような記述もないわけではなく、その辺はイロイロでありましょうが、とりあえず今回は「定数」の項についてのおはなし。
Lua は仕様上、定数を持つような言語機能を供えていない。けれども、実は通常のグローバル変数というのは _G というテーブルの要素でしかないこと、しかもテーブルへのアクセスには Lua からフックをかけることができるという事実、この2つを組み合わせることでグローバルな定数のようなものは実現できる。
簡単に方針を解説すると Lua では、特定のデータに対してメタテーブルというのを指定できる。そのメタテーブルのインデックスには __add とか __index のようなアンダースコア 2つで始まるキーを用意する。そうすると、そのデータに対して + を呼んだときには実際の処理のかわりにメタテーブルの __add に対応づけられた関数が、同じようにテーブルのルックアップ操作に対しては __index が呼ばれるようになる、というわけだ。 _G にはメタテーブルは設定されていないので、新しくそのようなテーブルを作って _G にかぶせてやればよい。
コード例は次のような感じで。
do local constants = {} local h = {} local function gettable_event(table, key) if constants[key] then return constants[key] else return h[key] end end local function settable_event(table, key, val) if not constants[key] then h[key] = val end end function define_constant(const_name, val) if not constants[const_name] then constants[const_name] = val end end setmetatable(_G, { __index = gettable_event, __newindex = settable_event }) end
ここで do 〜 end まではひとつのスコープを形成する。 constants と h はそれぞれスコープローカルな変数なので、外からは参照できない(でないと、 constants 自身を弄ることで定数に悪いことができてしまう)。で、ようは定数の名前空間のために constants というテーブル、通常のグローバル名前空間は h というテーブルを用意しておく。変数参照のときは constants と h を両方見るが、変数更新(代入)のときは h にしか入れない。で、 define_constantで constants に値をつっこむわけだ。簡単だね。
以下、利用例。
> loadfile("constant.lua")() > x = 1 > =x 1 > define_constant("y", 2) > =y 2 > y = 3 > =y 2
これはお手軽な実装なので、実際にはもう少し考えなければならない余地があるだろう。たとえば同じ名前の変数が存在するときに define_constant したらどうなるか?(今は単に昔のデータを忘れてしまう) ローカルスコープ限定の constant を定義するにはどうするか?(これにはちょっとアイディアがない) それからdefine_constant では名前を文字列で与えるというのもカッチョ悪いんだど、名前を渡すことはできないから仕方ないか?(たぶん無理) あと is_constant みたいな述語も定義した方がいいかもしれないとか。
というわけで、 Lua のはなしでした。
クレバーだけど最適化しにくそうなメカニズムですなあ>mettable