[LSP42] JSX
(この記事はLISP Implementation Advent Calendar 11日目のためのエントリです。)
JSXでLISPを作りました。
https://github.com/zick/JSXLisp
動機
今年の春、訳あって42個のプログラミング言語でLISP処理系を実装することになりました。これはその17個目です。
JSXという言語を選んだのは、やはり楽そうだったからです。以前、JavaScript系(*)の言語を使ったとき非常に楽な思いができ、もう一度あの時くらい楽が出来ないかなと考えていたら、JSXというJavaScript系の言語を見つけたので使わざるを得なかった訳です。
(*) ここでは、JavaScriptに変換できたりJavaScriptの代わりに使えるような言語のことだよ、お兄ちゃん!
外観
JavaScript系の言語だからきっと楽に書けるだろと思っていたらJSXのプログラムを見て度肝を抜かれました。
class Bar { function baz(str : string) : string { return str + '!!!'; } function qux() : string { return this.baz('foo'); } } class Foo extends Bar { var field_ : string; function constructor(field : string) { this.field_ = field; } override function qux() : string { return this.baz(this.field_); } } class _Main { static function main(args : string[]) : void { var foo = new Foo('woohoo!'); log foo.qux(); } }
工工工エエェェ(´д`)ェェエエ工工工
なんですか、このJavaに勝るとも劣らない冗長な記述は。関数には型を必ず付けないといけないのに function という非常に長いキーワードも書かなければならないというのがあまりにも理不尽です。 return がなければ戻り値の型が void なのは明らかなのに、それでもvoidと書かなければなりません。 function constructor
というのもいい味を出しています。mainに着目してみると、かの有名なJavaの
public static void main(String[] args)
と、JSXの
static function main(args : string[])
が非常にいい勝負をしているのが印象的です。あとJSXはJavaと異なり this を必ず書かなければなりません。もしかすると、JSXはJavaよりも冗長なコードを書ける可能性を秘めた数少ない言語の1つなのかもしれません。
JavaScriptとの連携
JSXのマニュアルを読んでも標準入力から文字列を取ってくる関数が見当たりませんでした。まあ、JavaScriptに変換される言語なんだから仕方ありません。JSXはNode.jsで動くので、JavaScriptの関数を直接呼んでやれば解決だろうと思ったら、なんと、JSXがJavaScriptのデータを正しく扱えずにエラーになります。マニュアルをいくら読んでも回避策が分かりません。色々と検索してようやくやり方が分かりました。
// setEncoding と on をもつJavaScriptのオブジェクトを扱うための型 native __fake__ class Stdin { function setEncoding(str : string) : void; function on(str : string, fn : variant) : void; }
JavaScriptのデータをうまく扱うために自分でインタフェースを定義してやらないといけないようです。まったく違う言語と連携するときには分かるんですが、なんでJavaScriptに変換される言語なのに、こんな面倒なものを記述しないといけないんですか。まあ、この記述は型安全のために我慢するとして、このやり方がマニュアルに載っていないというのはどういうことなんですか。いくらなんでもあんまりだと思います。
オブジェクト
文句ばかり書いてしまいましたが、ちゃんとLISPを作りました。今回はテーブルではなく、ちゃんとクラスを使ってLISPのデータ型を表現しました。
class LObj { var tag : string = 'NO TAG'; function str() : string { return ""; } function num() : number { return 0; } function car() : LObj { return new LObj; } function cdr() : LObj { return new LObj; } function set_car(obj : LObj) : void {} function set_cdr(obj : LObj) : void {} function args() : LObj { return new LObj; } function body() : LObj { return new LObj; } function env() : LObj { return new LObj; } function fn() : function (:LObj) : LObj { return (args : LObj) -> args; } } class Nil extends LObj { static var nil : Nil = new Nil; function constructor() { this.tag = 'nil'; } override function str() : string { return "nil"; } } class Num extends LObj { var num_ : number; function constructor(num : number) { this.tag = 'num'; this.num_ = num; } override function num() : number { return this.num_; } }
すべてのベースとなるLObjにすべての型のためのアクセッサが定義されているというおちゃめな実装です。クラスの種類ではなく文字列の tag でデータ型を区別します。「このクラスは食べられないよ。明日もう一度この場所にきてください。本当のオブジェクト指向を見せてあげましょう。」という方のために、もう少し普通っぽい実装も試してみました。
class LObj {} class Nil extends LObj { static var nil : Nil = new Nil; } class Num extends LObj { var num_ : number; function constructor(num : number) { this.num_ = num; } function num() : number { return this.num_; } } ... if (obj instanceof Cons) { ... } ... var num_obj = obj as Num; return num_obj.num();
はい。クラスはずいぶんスッキリしました。型の比較は文字列でなくなったので、型名のtypoなどはコンパイルで弾けます。さて、これを実行してみると、なんと驚きの結果。本のプログラムより1.5倍ほど遅くなります。細かい要因などは調べていませんが、どうも instanceof が特に遅いように見えます。詳しくは詳しい人に聞いてください。
小学生並みの感想
細かいところはともかく、マニュアルはしっかり書いて欲しいです。