Dolittle
から
JavaScript
へのトランスパイル実行
本多 佑希
1長 慎也
2大村 基将
1,3久野 靖
4兼宗 進
1 概要:Dolittleはプロトタイプ方式のプログラム言語であり,Classを定義せずにオブジェク トを扱えることから,中学校から大学などの多くの授業で利用されてきた.DolittleはJava で開発され,JavaアプレットによりWebでの実行も可能である.教育利用においてはイン ストールせずにWebブラウザから手軽に利用できることは重要な要件である.しかし,Java アプレットでは実習用の計算機にJavaをインストールしたりアプレットの実行を許可した りする設定を管理者権限で行う必要があり導入の障害になってきた.そこで,CoffeeScriptやTypeScriptなどのAltJSと同様に,Dolittleで記述されたプログラムをJavaScriptに動 的に変換(トランスパイル)して実行する方式を採用することで,Webブラウザから手軽に 利用できる環境を実装することにした.DolittleとJavaScriptの双方には,プロトタイプ方 式であること,オブジェクトの実体がハッシュであること,動的型付け言語であること,メ ソッドを手続きオブジェクトの代入により定義することなど,多くの共通点がある.また, トランスパイラとして実装することにより,Dolittleのプログラムを実行できることに加え, JavaScriptの各種機能をDolittleから呼び出して利用することが可能になった. キーワード:Dolittle JavaScriptトランスパイル コンパイラ
1.
はじめに
Dolittle1, 5)は教育用に設計された言語である. JavaScriptのようなprototype方式のオブジェク ト指向を採用することで,クラスやインスタンス などの概念を意識せず,オブジェクト指向を学ぶ ことができる. 教育利用においては,ローカルにインストール することなくWebブラウザから実行できること は有用である.DolittleはJavaで開発されている 1 大阪電気通信大学Osaka Electoro-Communication University
2 明星大学 Meisei University 3 静岡大学 Shizuoka University 4 筑波大学 University of Tsukuba ことから,JavaアプレットによりWebブラウザ で実行することが可能であった.しかし,近年で はJavaの実行環境を用意することが容易ではな くなってきたことから,JavaScriptで実行できる Dolittleの実装を検討することにした. 実装にあたっては,JavaScriptでインタプリタ を実装することも考えられたが,今回は Dolit-tle と JavaScriptの 言 語 的 な 類 似 点 に 着 目 し , TypeScript2)やCoffeeScript3)等のAltJSと同様 にDolittleをJavaScriptに変換して実行するトラ ンスパイル(source to source compile)する方式 を採用した.
本稿ではある言語で書かれたプログラムを,抽象 化レベルが同程度の別の言語に変換することをト ランスパイルと呼ぶ6).TypeScriptはJavaScript に静的型付けやモジュールといった概念を付加
しており,CoffeeScriptはJavaScriptにクラスの 概念や構文の簡潔さを付加している.本研究では JavaScriptに「プログラミング初学者でも扱いや すい」「教育利用に適している」といった,Dolittle の特徴を付加することを目的とする.
2.
Dolittle 言語とその実装
図1にDolittleの基本構文を示す4, 7).基本的 な構文は代入とメッセージ送信であり,簡潔に記 述できることが特徴である.この章では,Dolittle の言語仕様について述べる. プログラム::= (文 ’.’)… 文::= [変数’=’ ]式 変数::= [項’:’ ] 名前 式::=単純式|送信 送信::= [項] ’ !’ 電文 電文::=単純式 … 名前( [’;’]単純式 … 名前)… 括弧::= ’(’中置式’)’| ’(’送信’)’ 単純式::=数値定数|文字列|括弧|ブロック ブロック::= ’[’ [ ’|’名前…’|’ ]文( ’.’ 文)…’]’ 中置式::=中置式 演算子 中置式|項 項::=単純式|名前 図1 Dolittleの基本構文 2.1 メソッド実行 Dolittleのメソッド実行(メッセージ送信)の例 を示す. obj ! 100 (x) (x+2) msg. objはオブジェクト,100と(x)と(x+2)はパラ メタ,msgはメソッド名である.変数名と中置式 をパラメタに含める場合は括弧で囲んで記述する. メソッド実行はカスケードすることができる. obj ! msg1 msg2. 上の例は次の例と等価であり,objにmsg1を実 行した戻り値に対して,msg2が実行される. (obj ! msg1) ! msg2. 2.2 ブロック ブロックはコードを内包するオブジェクトであ り,生成されたときの環境を保持している. ブロックは単独で実行されるほか,オブジェク トのプロパティに代入することでそのオブジェク トのメソッドとして扱われる.また,後述するよ うに,条件分岐や反復などの制御構造を記述する ために用いられる. 次の例は,ブロックを実行する. [...] ! execute. 次の例は,ブロックを10回実行する. [...] ! 10 repeat. 次の例は,オブジェクトobjのmsgというプロ パティにブロックを代入することでメソッドを定 義し,それを実行している. obj:msg=[...]. obj ! msg. 引数は,次のようにブロックの先頭で取得する. blk = [|x y| ...]. blk ! 100 200 execute. 繰り返しの場合は,何回目の実行かというカウ ンタの値が渡される. [|n| ...] ! 10 repeat. 2.3 条件分岐 Dolittleにはifやwhileのような制御構文はな く,ブロックを用いたメッセージ送信のカスケー ド実行で制御構造を表現する. [...] ! then [...] execute. [...] ! then [...] else [...] execute. これらは次のように実行される. ([...] ! then) ! [...] execute. (([...] ! then) ! [...] else) [...] execute. ブ ロ ッ ク に then が 適 用([...] ! then が 実 行 )さ れ る と ,条 件 の 成 否 等 に よ っ て True/False/Doneの中間オブジェクトが返る. • True ! [...] execute は引数のブロックを実行する. • False ! [...] execute は引数のブロックを実行せずundefinedを 返す. • True ! [...] elseは引数のブロックを実行し,ブロックの実行 結果を内包したDoneを返す. • False ! [...] else は引数のブロックを実行せずTrueを返す. • Done ! [...] execute は引数のブロックを実行せず,自身が内包し ている値を返す. 2.4 オブジェクト間の継承 Dolittleはクラスではなくプロトタイプを用い てオブジェクト間の継承関係を持つ.rootはす べてのオブジェクトの親(プロトタイプ)であり, number, string, blockなどのオブジェクトがあら かじめrootの子として定義されている.
プログラム中でcreateを実行してオブジェクト を生成した場合,元のオブジェクトobj1は生成さ れたオブジェクトobj2の親になる.
obj2 = obj1 ! create.
定数や,計算結果などで暗黙的にオブジェクト が生成された場合は,stringやnumberなどのプ ロトタイプオブジェクトが親になる. str = "abc". x = 1 + 2. 2.5 プロパティの継承 オブジェクトのプロパティを参照した場合,そ のオブジェクトに該当するプロパティが存在しな い場合は親のプロパティを参照し,存在しない場 合はさらに祖先のプロパティを参照していく. メソッドもプロパティにブロックを代入する形 で定義されているため,同様の探索が行われる. すべてのオブジェクトの親はrootであるため, rootのプロパティはすべてのオブジェクトから参 照可能なグローバル変数として扱われる.次の代 入は,すべてrootのプロパティ「x」を定義する. root:x = 3. :x = 3. x = 3. 2.6 対応しているオブジェクト 表1に,現在JavaScript版で実装しているオブ ジェクトと定義されたメソッドを示す. • arrayオブジェクトは,Arrayをラップした ラッパーオブジェクトである. • stringオブジェクトは,String.prototypeにメ ソッドを追加し,メソッド拡張を行った. 表1 対応しているオブジェクトの例 オブジェ クト メソッド 説明 array create 配列を生成する size? 要素数を返す get 要素を参照する add 要素を追加する join 要素を連結した文字列を返す removepos 要素を削除する insert 要素を挿入する foreach 各要素に対してメソッドを実行する string add 文字列を連結する contain? 文字列が含まれているかを調べる timer create タイマーを生成する interval 実行間隔を指定する times 実行回数を指定する execute タイマーを実行する block then ブロックを評価し中間オブジェクト を返す repeat ブロックを指定回数実行する while ブロックを繰り返し実行する execute ブロックを実行する
3.
トランスパイラの設計と実装
3.1 トランスパイルの方針 1 章 で 述 べ た よ う に ,今 回 は Dolittle を JavaScript で実装するために,インタプリタを 作成して実行する代りに,JavaScriptへのトラン スレータを作成して実行する方式を採用した. DolittleとJavaScriptは,構文は異なっている が,言語仕様には類似する点が多い.これらの性 質を利用することで,JavaScriptでの実行が容易 に実現できると期待した. • プロトタイプ方式である • オブジェクトの実体がハッシュである • 動的型付け言語である • メソッドを手続きオブジェクトの代入で定義できる • グローバルなオブジェクトは,あるオブジェ クトのプロパティである • 関数(ブロック)は,実行された外側のスコー プを持つ 3.2 トランスパイルの実装 3.2.1 ブロックの変換 ブロックは,JavaScriptの匿名関数を用いて実 装を行った.Dolittleにおいてのブロックは実行 されたスコープを自らのスコープとして持つ.こ れは,JavaScriptの匿名関数においても同様の動 作をする.宣言時には実行されない点も同様であ る.図2と図3に,ブロックとメソッドの変換例 を示す. // Dolittle [100]. [|param| param + 100]. [|param; local_param| local_param=100. local_param+param ]. // JavaScript (function(){return 100;});
(function(param){return param!100 add;}); (function(param){ var local_param; local_param=100; return local_param+param; }); 図2 ブロックの変換 // Dolittle obj ! 100 (param) "str" msg . obj ! [func] msg. // JavaScript obj.msg(100,param,"str"); obj.msg(function(){return func;}); 図3 メソッドの変換 3.2.2 継承の実装 親子関係は,JavaScriptのprototypeを利用し た.createメソッドが実行されると,Object.create を使用し,自分を親とする子オブジェクトを生成 し,返す.親のプロパティを探しに行く仕様は, JavaScriptのprototypeチェーンを利用すること で対応した. 3.3 制御構造の実装 3.3.1 条件分岐 条件分岐は,JavaScriptのFunction.prototype にthenメソッドを追加し,Functionオブジェク トのprototype拡張を行うことで対応した.then メソッドを呼び出し,カスケードすることで条件 分岐を行う. 3.3.2 繰り返し 繰 り 返 し に 対 し て も ,JavaScript の Func-tion.prototypeにrepeatメソッドを追加し, Func-tionオブジェクトのメソッド拡張を行うことで対 応した.繰り返し文では,引数として渡された回 数分,呼出し元のブロックを実行する.その際に は,呼出し元のブロックに繰り返し回数を引数と して与える.呼出し元のブロックで引数を指定し ている場合,この繰り返し回数を引数として取る ことができる.
4.
議論
いくつかの機能に関しては,言語仕様の違い等 の理由から,互換性について検討を行っている. 4.1 タイマーオブジェクトの互換性 Dolittleのタイマーオブジェクトの繰り返し実行 は,スレッドにより実行される.しかし,JavaScript にはスレッドが存在しない.本研究で実装したタ イマーオブジェクトの繰り返し実行も,JavaScript のsetInterval関数を用いて実装したため,スレッ ドでの実装にはなっていない. Dolittleのタイマーオブジェクトには,waitメ ソッドが存在する4, 5).これは,呼び出し元のタイ マースレッドが処理を終えるまで,メインスレッド の動作を止めて待ち合わせる.しかし,このwaitメソッドをJavaScriptで実装することは困難で ある. そこで,「タイマーの終了までメインスレッドを 止める」のではなく,図4のように「タイマーの 終了時に実行するメソッド」を指定する機能を提 供することで,実用上はwaitに近い用途を実現す ることを検討している.
clock = timer ! create. clock ! [...] execute. clock ! wait.
label ! ”hello” create. clock = timer ! create. clock ! [...] execute.
clock ! [label ! ”hello” create] after ececute.
図4 タイマーのwaitの対応 4.2 prototype汚染 JavaScriptにおいて,Functionオブジェクトや Stringオブジェクト等のprototypeにメソッドを 追加してメソッド拡張を行うことはあまり良いも のではないとされている.この問題は,ラッパー オブジェクトを設計し,提供することで解決が可能 である.しかし,その場合はトランスパイルが複 雑になってしまう.そのため,本研究ではメソッ ド拡張を行うことにした. 以下に,Functionのprototypeを拡張した場合 と,ラッパーオブジェクトを実装した場合の,ブ ロックのトランスパル例を示す. • トランスパイル前 // Dolittle obj:func=[|param| self:myField ]. • トランスパイル後 // JavaScript:prototype拡張 obj.func=(function(param){ return this.myField; })); // JavaScript:ラッパー obj.func=(new Block(function(param){ return this.myField; })); 上のobj.funcを呼び出す文は以下のようにトラン スパイルされる. • トランスパイル前 //Dolittle obj:func!(param) execute. • トランスパイル後 //JavaScript:prototype拡張 obj.func(param); //JavaScript:ラッパー obj.func.execute(param); この呼び出しは,Blockオブジェクトにexecuteメ ソッドを追加しておくことで実現可能である.し かし,Dolittleにおいては,次の例のような方法で もメソッドを呼び出すことができる. //Dolittle obj!(param) func. これをJavaScriptにトランスパイルする場合, pro-totype拡張とラッパーを用いた場合では次のよう にトランスパイルの方法を変える必要がある. // JavaScript:prototype拡張 obj.func(param); // JavaScript:ラッパー obj.func.execute(param); しかし,ラッパーを用いた場合には次のような問 題がある.
• executeに渡されるthisの値がobjではなく,
obj.funcになってしまう. • obj.funcが本当のFunctionオブジェクトの場 合は実行できなくなる(つまり,Functionオ ブジェクトをすべてラップをする必要がある. JavaScriptから生のデータを受け取るのが難 しくなる.) これらの問題を考慮して,Functionオブジェクト ではprototype拡張を行うことにした. ただし,他の組み込みオブジェクトに関しては prototypeの拡張は可能な限り行わず,ラッパーを 作成することを設計の指針としている. 例えば,配列についてはJavaScriptのArrayク ラスを直接利用するのではなく,ラッパーを作成 した.理由としては次のような点が挙げられる.
• JavaScriptの配列は,要素の添字が0から始 まる(0-originである)が,ドリトルの配列は, 1-originである.このため,Arrayクラスの メソッドを拡張してしまうと,添字の仕様が 0-originのものと1-originのものが混在して しまう. • ドリトルにはJavaScriptにおけるa[i]のよう な,配列の要素にアクセスするための特別な 構文がなく,単なるメソッド呼び出しで要素 にアクセスするため,ラッパーの実装が簡単 である. このような,組み込みオブジェクトの性質を踏ま えて,ラッパーを利用するかprototype拡張をする かを臨機応変に採択する方針で設計を進めていく.