[LSP42] DとJavaとC#
(この記事はLISP Implementation Advent Calendar 14日目のためのエントリです。)
DとJavaとC#でLISPを作りました。
https://github.com/zick/DLisp
https://github.com/zick/JavaLisp
https://github.com/zick/CSharpLisp
動機
今年の春、訳あって42個のプログラミング言語でLISP処理系を実装することになりました。これはその22〜24個目です。
JavaとC#は明らかにメジャーな言語ですし、Dは少なくても名前は知っている人は多いでしょう。こういった言語を使ってもあまり喜ぶ人も少ないのですが、42個のうち半分を超えたのでいまのうちにこっそり使っておこうという発想で選びました。42個目の言語がJavaだったりするとあまりにも残念な感じになってしまいますから。
Dの思い出

Dはまったく見たことも書いたこともなかったのですが、Cを知ってたらほとんど新しいことを勉強しなくても書けてしまいました。コンパイラのエラーメッセージも読みやすいので楽ちんです。
外観
だいたいCみたいな感じです。
LObj nreverse(LObj lst) {
LObj ret = kNil;
while (lst.tag == Type.Cons) {
LObj tmp = lst.data.cons.cdr;
lst.data.cons.cdr = ret;
ret = lst;
lst = tmp;
}
return ret;
}
便利な機能を使う時以外はCと思って書けば書けます。私にとっては非常に楽でした。
データ構造
例によってクラスを1つだけ作るというインチキを使いました。
enum Type {
Nil, Num, Sym, Error, Cons, Subr, Expr
}
class LObj {
struct Cons {
LObj car;
LObj cdr;
}
...
union Data {
int num;
string str;
Cons cons;
Expr expr;
Subr subr;
}
this(Type type) {
tag = type;
}
...
this(Type type, LObj a, LObj d) {
tag = type;
data.cons.car = a;
data.cons.cdr = d;
}
...
Type tag;
Data data;
}
今回はtagにenumを使っているので、少なくても文字列よりは間違いが起きないでしょう。Fantomのような名前付きコンストラクタはないので、コンストラクタの第一引数にはtagを付けるようにしました。あとは特筆すべき点はないかと思います。
SUBR
まるでCの関数ポインタのようなことができるのでSUBRも楽に作れます。
LObj subrCons(LObj args) {
return makeCons(safeCar(args), safeCar(safeCdr(args)));
}
...
addToEnv(makeSym("cons"), new LObj(Type.Subr, &subrCons), g_env);
ただ、「クロージャのポインタを取得するには delegate を使って云々……」みたいなことがマニュアルに書いてあったのですが、面倒そうなのでクロージャは使わないという方向で逃げました。
Javaの思い出

Javaは一応書いたことはあるのですが、Javaアプレットやiアプリ、それをMIDP用に書き直したもの、などの簡単なゲームをちょっと書いたことがある程度です。特にiアプリではメモリや速度の関係で「staticおじさん」にならざるを得ない感じでしたし、それどころか else を削ることでバイトコードを節約するなど、通常のJavaとは違う何かを書いただけのような気もします。あと、大学生に「iアプリって何ですか」と先日言われたのがなんだかショックで仕方ありません。
外観
Dと比べると冗長な感じがします。
public static LObj nreverse(LObj lst) {
LObj ret = kNil;
while (lst.tag() == Type.CONS) {
LObj tmp = lst.cons().cdr;
lst.cons().cdr = ret;
ret = lst;
lst = tmp;
}
return ret;
}
さっそくstaticおじさんになっております。
データ構造
クラスを1つしか作らないのはDのときと同じですが、Javaには共用体がありません。
enum Type {
NIL, NUM, SYM, ERROR, CONS, SUBR, EXPR,
}
class LObj {
public Type tag() { return tag_; }
public LObj(Type type, Object obj) {
tag_ = type;
data_ = obj;
}
public Integer num() {
return (Integer)data_;
}
...
public Cons cons() {
return (Cons)data_;
}
...
private Type tag_;
private Object data_;
}
class Cons {
public Cons(LObj a, LObj d) {
car = a;
cdr = d;
}
public LObj car;
public LObj cdr;
}
しかし、 Object というすべてのベースとなるクラスがあるので、キャストするだけです。型安全とか知りません。
SUBR
ちらっと人のコードを見たところ、Javaはインスタンスを作るときにクラスの一部分をオーバライドできるらしいじゃないですか。気になったのでこれを使ってSUBRを作りました。
Subr subrCons = new Subr() {
@Override public LObj call(LObj args) {
return Util.makeCons(Util.safeCar(args),
Util.safeCar(Util.safeCdr(args)));
}
};
実際にはinner-classが作られているんでしょうか。内部的なことは分かりません。
あと、最近のJavaにはクロージャがあるとかないとかいう話は知りません。
C#の思い出

C#は書いたことがなかったのですが、それよりもいくら探してもC#のロゴらしきものが見当たらなかった方が問題です。だれかC#のロゴが何なのか教えてください。
外観
Javaと同程度の冗長さでしょうか。
public static LObj nreverse(LObj lst) {
LObj ret = kNil;
while (lst.tag() == Type.Cons) {
LObj tmp = lst.cons().cdr;
lst.cons().cdr = ret;
ret = lst;
lst = tmp;
}
return ret;
}
といより、ほぼJavaと同じに見えます。
データ構造
Javaのときと完全に同じ戦略です。データはすべて object というすべてのベースとなるクラスにキャストします。型安全とか知りません。
enum Type { Nil, Num, Sym, Error, Cons, Subr, Expr };
class LObj {
public Type tag() { return tag_; }
public LObj(Type type, object obj) {
tag_ = type;
data_ = obj;
}
public Int32 num() {
return (Int32)data_;
}
...
public Cons cons() {
return (Cons)data_;
}
...
private Type tag_;
private object data_;
}
class Cons {
public Cons(LObj a, LObj d) {
car = a;
cdr = d;
}
public LObj car;
public LObj cdr;
}
SUBR
よく分かりませんが、delegateというのを使えばクロージャが作れるらしいです。Dのときに同じ話を聞いた気もします。
delegate LObj Subr(LObj ags);
Subr subrCar = delegate(LObj args) {
return Util.safeCar(Util.safeCar(args));
};
何をやってるのかよく分かりませんが、こんなんで動きました。
小学生並みの感想
「よく分からないけど動く」というのはある意味非常に大事なことだと思います。