2014/04/26 STAFF BLOG
KRPanoスクリプトは簡易的なスクリプト言語ですが、プログラミング言語に必要な要素をひと通り実装しています。変数、条件分岐、繰り返し構造、関数定義、各種入出力など、仕様を見てみるとおよそ世間のプログラミング言語とそう変わらない機能ラインアップです。それどころか、プラットフォームを選ばずFlashでもHtml5でも動くという点では強力な汎用性を備えた言語であるとも言えます。
今回はそんなKRPanoスクリプトでオブジェクト指向風プログラミングを実験してみます。もちろん、KRPanoスクリプトはオブジェクト指向言語ではないので、諸々トリックで実現していくことになります。まずは、以下のコードをご覧ください。
<krpano version="1.16" onstart="startup();"> <!-- testクラスの定義 --> <class name="test" property="123" /> <action name="startup"> showlog(); <!-- class['test']のプロパティを表示 --> trace( 'show property:', class['test'].property ); </action> </krpano>
前回のサンプルコードとほとんど同じですが、独自タグの名前を<class>としているところがポイントです。もちろんKRPanoの標準タグの中に<class>などという定義はありません。つまり、これはKRPano上でクラス定義をしているつもりなのです。
KRPanoではすべての変数がグローバルスコープに配置されますが、上記のようにclassタグの中にpropertyを含めることでOOP的なカプセル化が実現できました(またname属性が連想配列として機能しているところもポイントです)。
色々なツッコミがあるかと思いますが、更に次のコードを見てください。
<krpano version="1.16" onstart="startup();"> <!-- testクラスの定義 --> <class name="test" property="123" action="trace('action called:', %1 );" /> <action name="startup"> showlog(); <!-- class['test']のプロパティを表示 --> class['test'].action( class['test'].property ); </action> </krpano>
クラス定義の中にメソッドを追加しました。これで一段階、オブジェクト指向らしくなります。
KRPanoの公式マニュアルによるとアクション定義は<action>タグを使用するとされていますが、実はこれは便宜的な仕様で必ずしも<action>タグである必要はありません。KRPanoスクリプトをご存じの方にとっては意外だと思われるかもしれませんが、例えばレイヤーのイベントハンドラなどでonclickプロパティにアクションを記述できることを思い出していただければ納得できるかと思います。
この事実は、KRPanoスクリプトが非常に柔軟な言語であることを意味しています。公式マニュアルには使用可能なタグの説明が出ていますが、実は言語的にはタグの定義名は何でもよく、識別子に「()」さえつければ、何でも関数として呼び出すという仕様なのです。また、KRPanoの世界では型がなく、全てが等しくオブジェクトとして扱われます。C言語風に捉えるならば、識別子は全てポインタで「()」は関数呼び出しの演算子という扱いなわけです。(正確にはオブジェクト内容を文字列として解釈し、関数とみなす演算子といったところでしょうか)
KRPanoにとって関数がただの文字列であるという証拠が以下のコードです。
<krpano version="1.16" onstart="startup();"> <action name="startup"> showlog(); set( function, 'trace( function called );' ); function(); </action> </krpano>
JavaScript風に変数「function」に関数定義を代入して実行することができてしまいます。なんと自由な言語でしょう。(無名関数の実装はできないみたいですが)
しかし、先ほどのソースコードをオブジェクト指向というには、まだまだ突っ込みどころがあります。このままだとスタティックプロパティやスタティックメソッドしかないクラスになってしまいます。次のコードではクラスのインスタンスを作ってみます。
<krpano version="1.16" onstart="startup();"> <!-- testクラスの定義 --> <class name="test" property="123" action="trace('action called:', %1 );" /> <action name="startup"> showlog(); <!-- インスタンス化 --> copy( instance1.property, class['test'].property ); copy( instance2.property, class['test'].property ); copy( instance1.action, class['test'].action ); copy( instance2.action, class['test'].action ); <!-- それぞれのインスタンスに値をセット --> set( instance1.property, 'def' ); set( instance2.property, 'ghi' ); <!-- それぞれを表示 --> instance1.action( instance1.property ); instance2.action( instance2.property ); </action> </krpano>
実装方法を考える中で一番悩んだのは、このインスタンス化です。というのもKRPanoには別段「new」コマンドがあるわけではないからです。その「new」の代わりに使えるのがcopyコマンドです。公式マニュアルによると、copyコマンドは変数の内容を変数にコピーする命令と書かれていますが、実はオブジェクトをコピーする命令です。変数にかぎらずあらゆるオブジェクトをコピーすることができます。内容が数値や文字列でなくとも、アクション定義ですらコピーできてしまいます。この性質を利用して、クラス定義をまるごとコピーすることでインスタンス生成を実現してみました。そういう意味ではよりシンプルに
copy( instance1, class['test'] ); copy( instance2, class['test'] );
とすればいいように思うのですが、どうも階層のあるオブジェクトの場合、実体コピーではなく参照コピーとして動作してしまうらしく、オブジェクトの実体が共有されてしまうようです。苦肉の策として、各プロパティ、メソッドごとにコピーするという手にしました。(あまりかっこよくないですが)
インスタンスが単なるクラス定義のコピーなので、これはどうもエセオブジェクト指向なのですが、言語仕様の柔軟さを利用するとこのようなことも実現できるわけです。コピーの方に別々のプロパティやメソッドを追加すれば継承もどきもできますし、なんとか使い物にはなりそうです(関数の引数は定義できないので、ポリモーフィズムは難しい)。
余談ではありますが、実装を考えながら「オブジェクト指向はプログラミングスタイルであって、言語仕様ではない」という教えを受けたのを思い出しました。