このページの内容

Q&A

なぜWrenを作ったのですか? #

ほかの創作活動は、始めた途端に存在意義を問われたりしませんが、なぜかプログラマーは、新しい言語を好まないようです。私が埋めようとしている分野は、次のとおりです。

アプリケーションへ組み込むために使う、スクリプト言語はいくつかあります。中心はLuaです。以前はTCLでした。Guileもあり、JavaScriptも増えていて、Pythonを組み込むアプリケーションもあります。私は元ゲーム開発者なので、「スクリプト」と聞くと、「ゲームのスクリプト」を考えがちです。

Luaは良い言語です。小さく、単純で、高速です。しかし、批判するつもりではありませんが、C++やJavaなどに慣れていると、変わった言語でもあります。構文が違います。意味論、特にオブジェクトのモデルは独特です。1から始まるインデックスには誰でも慣れますが、メタテーブルなどを見ると、Luaへ後からオブジェクトを付け加えたことが、よく分かります。

Luaと同じくらい単純で、オブジェクト指向の経験がある人に自然に感じられる言語には、余地があると思います。Wrenは、それを目指す私の試みです。

なぜクラスなのですか? #

JavaScriptの人気のおかげで、多くの人が今、プロトタイプを知り、その方式の人気が高まっています。プロトタイプは面白いと思いますが、数年間試した後、プロトタイプを考案した元のSelfプロジェクトの多くの人々と同様に、クラスの方が使いやすいと結論付けました。

Luaで、そのようなオブジェクト指向のプログラミングを行う例を示します。


-- account.lua

Account = {}
Account.__index = Account

function Account.create(balance)
   local acnt = {}             -- our new object
   setmetatable(acnt,Account)  -- make Account handle lookup
   acnt.balance = balance      -- initialize our object
   return acnt
end

function Account:withdraw(amount)
   self.balance = self.balance - amount
end

-- create and use an Account
account = Account.create(1000)
account:withdraw(100)

同じ例をWrenで示します。


//account.wren

class Account {
  construct new(balance) { _balance = balance }
  withdraw(amount) { _balance = _balance - amount }
}

// create and use an Account
var account = Account.new(1000)
account.withdraw(100)

クラスは複雑だと言われますが、それは、クラスを持つ広く使われた言語のほとんど、C++、Java、C#、Ruby、Pythonが、とても複雑だからです。複雑なのは、その言語であり、クラス自身ではないことを、Wrenで示したいと思います。

それらの多くに影響を与えたSmalltalkは、単純なことで有名です。構文は、索引カードに収まります。私の目標は、Wrenをそれほど小さく保ちながら、クラスの表現力も持たせることです。

なぜバイトコードへコンパイルするのですか? #

詳しくは性能のページにありますが、短く言えば、バイトコードは性能と単純さの良い折り合いだからです。ほかにも、次の理由があります。

  • iPhoneやゲーム機などの多くの機器は、実行時に生成したコードの実行を許可しないため、JITコンパイルを使えません。

  • ファイバーは、とても強力な道具だと思います。ネイティブスタックを使わないバイトコードVMなら、その実装は簡単です。

なぜVMはレジスター方式ではなく、スタック方式なのですか? #

バイトコードVMには、二つの方式があります。スタック方式のVMは、短い、通常1バイトの命令を持ち、そのオペランドは暗黙にスタックの先頭にあると分かります。そのため、何かをスタックへプッシュするいくつかの命令と、その後に何かを行う命令が並ぶことがよくあります。

レジスター方式のVMには、大きな、通常32ビットの命令があり、オペコードと、スタック内のオペランドの場所を示すいくつかの数値を含みます。便利なのは、例えば次のLuaの文を、


a = b + c

一つのバイトコード命令にできることです。スタック方式の言語なら、bをプッシュ、cをプッシュ、加算、aへ保存、という四つになります。(ただし、どちらもコードは合計32ビットである点に注意してください。)

Luaは、以前はスタック方式でしたが、レジスター方式へ変えて高速になりました。なぜWrenもレジスターを使わないのでしょうか?

私は、以前、レジスター方式のVMを実装しました。良いモデルだと思いますが、Wrenには大きな利点をもたらさないと思います。コンパイルにより多くの手間がかかり、私は、Wrenの実装をできるだけ単純に保とうとしています。

その複雑さと引き換えに、命令数を減らせます。しかし、Wrenは、その利点を使えないと思います。現在、Wrenには算術演算専用の命令がありません。演算子は通常のメソッド呼び出しであり、利用者が定義した手続きを呼び出すこともできます。

メソッドの呼び出し規約は、すべての引数を呼び出し元のスタック先頭へ置くことを必要とします。それを、呼び出し先のスタックフレーム領域の底にできるようにするためです。Wrenで+を呼び出すには、やはり引数をスタックの先頭へプッシュする必要があります。同様に、呼び出し規約は、戻り値を最初の引数があった場所へ置くので、呼び出し後、それを宛先スロットへ戻す必要があります。

利用者定義の演算子メソッドへフォールバックする前に、組み込み型を特別扱いする算術命令を用意する価値はあるかもしれません。(Luaは、言語の開発後期に演算子のオーバーロードを追加したので、そうしていると推測します。)そうなれば、レジスター方式へ移れるかもしれません。

しかし、実際に性能上の利点になるとは確信していません。レジスター方式のVMが有利かどうかは、言語の細かな仕様の多くに左右されます。例えば、代入はLuaでは文ですが、Wrenでは式なので、効率的なレジスター方式のコードへコンパイルするのが難しくなります。

ほかに作った言語は、どうするのですか? #

私が誰かを知らなければ、奇妙な質問でしょう。以前、FinchやMagpieなど、ほかにも趣味の言語を作り、ブログへ書きました。

Finchは、インタープリターの実装と、プロトタイプ方式を、もっと知るために始めました。どちらについても、多くを学びました。特に、私はプロトタイプよりクラスを強く好むと分かりました。Finchへクラスを後付けし始めましたが、大きすぎる変更だと気付き、こうしてWrenが生まれました。

私にとって、WrenはFinchの代わりです。名前を変えた主な理由は、ほかの人がFinchを引き継いで何かしたい場合に、残しておくためです。私には、もうFinchで作業する意図はありません。

Magpieは、もう少し難しい話です。私は、Magpieの考え方がとても好きです。多くの場面で、こんな汎用言語が欲しいと思う言語です。パターンマッチングと多重ディスパッチが大好きです。libuvのイベント駆動IOを、ファイバーの単純さと結び付けるところも気に入っています。

しかし、はるかに難しいプロジェクトでもあります。汎用言語なので、何かに使えるようになるまでに、大量のライブラリー作業が必要です。GCには、未解決の問題があります。そして、正直なところ、今の私には、多重ディスパッチを効率的に実装するだけの技量がありません。

その間、Magpieを作り始めてから、Juliaが現れ、Dylanが再び現れました。Magpieを作った理由の一つは、多重ディスパッチの火を受け継ぐことでしたが、今は、ほかの人たちが、その光を広げ始めています。