2011/07/04

Lua が気になるお年頃

突然ですが、Lua が気になるお年頃がやってきました。たぶん思春期です。

TIOBE Programming Community Index for June 2011 によると、Lua は人気の高いプログラミング言語・第 10 位だとか。

派手さや嫌味のないシンプルでスマートな言語でありながら、連想配列クロージャなどのナウい仕様も備わっており、さらに数あるスクリプト言語の中でも屈指の実行速度を誇っているそうです。

まぁここまでは人から聞いた話ですが、聞けば聞くほど気になってしまい、たまたま Lua も手元にある事だし(なんであるの!?)、せっかくだからちょっとつまみ食いしてみる事にしました。

ちなみに、今回は Lua 5.1.3 に、Visual Studio 2008 Professional Edition を使いました。たぶんもっと新しいバージョンも探せばどこかに転がっていると思います。



【メタテーブルはおもしろい】

練習がてら、Lua のメタテーブルを使って「ベクトルクラス」的なもの(PVector)を作ってみました。

さっきも書きましたが、Lua は動的型付けと柔軟なテーブルが特徴的なスクリプト言語です。C 言語では様々な型を集成するために、構造体を定義する必要がありましたが、Lua ならばひとまとめにしたいデータを全てテーブルにぶち込んでおくだけでよいのでラクといえばラクです(まだ静的型付け言語に慣れた身としては、正直『何が入っているのか判らない気持ち悪さ』も多少ありますが)。

Lua におけるテーブルにはいくつかの糖衣構文が用意されていて、たとえばキー("key")を用いてテーブル(table)を参照する table["key"]table.key と等しく、さらに、キーが関数である場合、その呼び出し table.key(table)table:key() に等しいのだそうです。この仕組みを駆使すれば、OOP におけるクラスにかなり近いものが作れます。

さらに、メタテーブルというやや高度な仕組みがあり、これを用いる事で要素を参照する際の挙動をカスタマイズできたり、C++ でいうところの演算子をオーバーロードを Lua で実現したりできるようになるのです。

  1. -- PVector クラス  
  2. PVector = {}  
  3.   
  4. -- コンストラクタ  
  5. function PVector:new(x, y, z)  
  6.     -- インスタンスとして使用するテーブル  
  7.     local t = {}  
  8.       
  9.     -- メンバ変数の初期化  
  10.     t.x = x or 0  
  11.     t.y = y or 0  
  12.     t.z = z or 0  
  13.       
  14.     --   
  15.     PVector.__index = PVector  
  16.     setmetatable(t, PVector)  
  17.       
  18.     return t  
  19. end  
  20.   
  21. --[[ メンバ関数 ]]  
  22.   
  23. -- 新しい値のセット  
  24. function PVector:set(newX, newY, newZ)  
  25.     self.x = newX or 0  
  26.     self.y = newY or 0  
  27.     self.z = newZ or 0  
  28. end  
  29.   
  30. -- 文字列に変換  
  31. function PVector:to_string()  
  32.     return "(" .. self.x .. ", " .. self.y .. ", " .. self.z .. ")"  
  33. end  
  34.   
  35. -- 「+」演算子のオーバーロード  
  36. PVector.__add = function(v1, v2)  
  37.     return PVector:new(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z)  
  38. end  

コンストラクタ(PVector:new() 関数)では、setmetatable() を用いてインスタンスを表現するテーブル t に、メタテーブル PVector を関連付けています(ちなみにこのチュートリアルでは、setmetatable(t, {__index = PVector}) 的な書き方も紹介されています。こちらもおおよそ同じように振る舞います)。

これによって、返却されたテーブル t を経由して、PVector の各種メソッドを呼び出したり、PVector のメタメソッド(__add)によって、+演算子を用いたインスタンス同士の加算ができるようになります。

Lua の関数は、仮引数と実引数の数が一致していなくても特に問題はありません。たとえば、関数 PVector:new(x, y, z) に 2 つの引数を渡して呼び出した場合、最後の z 要素には nil が代入されます(今回は要素が最大 3 であるため、このような書き方をしましたが、完全に可変長の引数をとる関数の場合は、function f(...) のように、... を用いて記述します。このとき関数側では、引数群は { ... } というテーブルに格納される事になります)。

これによって関数のオーバーロードができなくなりそうな気もしますが、同じく Smalltalk の流れを汲む Objective-C だってその制約を持っていながら、NEXTSTEP や Mac を支え続けてきた実績がありますので、必ずしもこの仕様が言語としての欠陥に直結するわけではないのかな、と。

閑話休題。実引数が少ない場合、足りない分は関数呼び出し時に nil で埋められるという話ですが、nil が代入されたままだと計算時に厄介ですから、コンストラクタでは便宜的に 0 を代入する事にします。判定には if 文を用いてもよいのですが、Lua にはより簡潔な構文が存在します。

  1. t.x = x or 0  

これは、(C言語っぽく書くと) if (x != nil && x != false) { t.x = x; } else { t.x = 0; } と本質的に等価です。

で、このクラスを実際に使うとこんな感じになりました。
たしかに、(1, 2, 3) + (4, 5, 6) = (1 + 4, 2 + 5, 3 + 6) = (5, 7, 9) なので、ちゃんと+演算子が使えている事がわかります。

ちなみに継承なんかも、スーパークラスのインスタンステーブルに追加機能をぺたぺたくっつけていけばできそうな気がします



【C++ にバインド】


【C++ 側で定義した関数を Lua から呼び出す】

Lua はちっちゃな言語なので、単体で大きなプログラムを開発するにはやや不向きです。

どちらかというと、C 言語や C++ など、生産性の低い高級言語の補助に使うと幸せになれそうです。

そこで、試しに C++ の関数を Lua から呼び出す実験をしてみました。


このプログラムは、引数や戻り値の受け渡しを伴う C++ の関数を Lua から呼び出しています。実行したい関数は TestFunction() なのですが、そのままでは Lua からこの関数をコールする事ができないので、C++ と Lua の仲立ちをするグルー関数TestFunctionGlue())を作って、それを Lua にバインドしています。

C++ と Lua のデータの受け渡しには、スタックと呼ばれるデータ構造を用います。これは、データの表現形式が異なる C と Lua を仲介するために Lua 側が用意したメモリ領域で、関数の引数やら戻り値やらはここに積まれます。

というわけで、処理が正常に終了しようがするまいが、スタックの状態をちゃんと元に戻さないといずれスタックオーバーフローしてしまいますので、上記のコードでは lua_gettop() / lua_settop() で挟み打ちにして、スタックが爆発しないようにしています。

ところで、このプログラムを Visual Studio 2008 Professional Edition でビルドしようとしたら大変なことになりました(エラーメッセージがあまりにもひどいので折りたたんでおきます)。
うひー∩( ・ω・)∩ なにこれありえない。

Lua のライブラリ(lua5.1.lib)を自前の処理系でリビルドしたら、きれいさっぱりエラーが消えて実行できるようになりました。めでたし。


【Lua 側で定義した関数を C++ から呼び出す】

今度は逆に、Lua 側で関数を作って、それを C++ から呼び出してみました。

Lua の方はちょっとだけ長くなったので、以下のようにスクリプトファイル script.lua にまとめて書きました。


で、これを呼び出す C++ 側のコードはこんな感じ。


実行すると、こんな感じになります。
今回は main() 関数にべた書きしましたが、たとえばこんな風に書けば(lua_State* がグローバルなのが気に食わないものの)、もうちょっと解りやすくなるかなぁ。




ほかにも、C++ 側の変数を Lua が書き換えたり、その逆もできたりするのですが、そうはいってもこの時点ではまだ Lua の良さが今ひとつ見えてきませんね。

C++ にバインドできるのはよいのですが、そのための準備が非常に面倒(Lua VM を生成したり、グルーコードを書いたり、データを受け渡すためにスタック操作をしたり……)。

そこらへんをなんとかする方法についても調べて書きたいのですが、そろそろ息切れし始めたので今日はこの辺で。

0 件のコメント:

コメントを投稿

ひとことどうぞφ(・ω・,,)