Luaでusingディレクティブを実現する1

タイトルの通り、近代の高級言語ではだいたい備わっているであろうusingディレクティブを、Luaで実現しようって話ですが、はてな日記エントリーの id:gintenlabo:20100709#1278697770 で既にやってみた方がいらっしゃるのはLuaで実際に開発に携わってる方とかはご存知だと思います。知っての通り、Luaにはディレクティブなんて概念は無く、全てがチャンクと呼ばれるステートメントやブロックの処理単位で動作するので、特殊な機能は関数とテーブルをトリッキーに使って実現して下さいっていう感じですよね。


とにかくLuaは余計な言語仕様を排除して、ミニマリズムな仕様(構文や変数)とシンタックスシュガーを武器に、他のスクリプト言語の追随を許さない軽量さを誇っています。そこに痺れる憧れ…る?


Luaではメタテーブルという仕様で、テーブルやユーザ定義型の振る舞いを制御して、いわゆる演算子オーバーロードやプロパティに相当する機能を実現でき、環境テーブルという仕様で帯域変数へのアクセス方法などを制御して、いわゆるレキシカルスコープとか名前空間といった識別子の構造化が実現できます。


ただし、(当然ながら)局所変数の方が環境テーブルよりも名前解決の優先順位が高いので、実際に行われる名前解決は、

現在のスコープの局所変数から探す→より外側のスコープの局所変数から探す→…
(ローカル変数が見つからない場合は→)
環境変数(厳密には関連付けされた環境テーブル、デフォルトでは_G)から探す
(環境テーブルのメタテーブルに、テーブルアクセス用の設定がされている場合は→)
インデックス設定(テーブルないし関数)を通じて変数の値を得る

といった手順で行われると思っていいです。こう書くと変数アクセスの為に物凄いオーバーヘッドがかかってるように見えますが、あくまで構文上の説明であって、実際はもっとうまく名前解決されてるし、ローカル変数はチャンクの分析時点でレジスタやスタックに適切にマッピングされるので積極的にローカル変数を使いましょうって話。


usingディレクティブを実現したいってのは要するに、

function f(t)
  table.insert(t, "new value")
  
  -- ...

  table.sort(t)
end

とかいう時に、テーブル名ドットのプレフィックスを省略して、(但し関数スコープ内のみで有効な)

function f(t)
  using(table)
  insert(t, "new value")

  -- ...

  sort(t)
end

なんて出来たら記述量が大幅に減って、文脈によっては見た目も凄くスッキリします。こんな機能が是非欲しい。環境テーブルとメタテーブルをうまく連携させれば実現できそうで、要するに帯域変数参照時の名前解決部分が、

関連付けられた環境テーブルから変数名を探そうとするがメタテーブルで定義されたアクセス関数で制御される
→通常通り環境テーブルから探す→ルックアップに指定されたテーブルから順番に探す

という感じで介入される感じですね。
『通常の環境テーブルから探す』の優先順位も任意にできるんですが、この順番が一番都合が良さそうです。(そうでないとreturn文以外でスコープ外へ変数を渡したい時に面倒になる)
変数代入時は普通に環境テーブルに代入してやれば良さそうです。


gintenlaboさんもこういった方法でルックアップをうまいこと実現されてて関心したんですが、どうにも関数内でこのusing関数を呼んだ時に幾つか問題が起こるようで、一つはスコープ解決が出来てない為に、関数のスコープ外(一番外側のチャンクとか)でも名前干渉が起こってしまう。もう一つは、関数内で動的生成されたテーブルをusingで設定して、この関数が何度も呼ばれると、内部のルックアップリストが増え続けて著しい速度で解放されないメモリ量も増加します。(ガベージコレクタをフル起動させても使用中のメモリは解放しようがない)


別にバグというバグではなく、あくまで使い方の問題なので、スコープに関係無くルックアップしたい場合とか、モジュールテーブルしかルックアップしない場合は全然問題無いです。ただ僕はむしろスコープ内アクセスとか任意のテーブルをルックアップとかいう使い方をしたいので、これを解決する事を念頭に実装してみました。


100行程度のコードなんですが、前置きが無駄に長くなったので後日コード掲載と説明します。