[348] Re:プログラミング言語を作る
投稿者:mizu
2007/02/20 02:13:25
> うーん、私はLispはEmacs Lispやxyzzy Lispで遊んだ程度ですので、
>Lispそのものについては何も言いませんが(Paul Grahamのエッセイは日本語訳された
>範囲ではたぶん全部読んでますし、Lispを悪く言うつもりはないですが)、
私も、Lispは、Schemeとかで遊んだ程度なので、あまり偉そうには言えないです。
>コンパイラコンパイラが使える環境なら、多少の構文の複雑さは問題にならない
>はずです。
確かにそうだと思います。しかし、構文を少しずつ拡張していくという作業は、
いかにパーザジェネレータを使っていても、面倒ではあります。その点、Lisp系なら
一度S式パーサを書いておけば、基本的にはパーザをいじらなくても構文を拡張
できる(まあ、S式で書けるものしか基本的には書けませんが)というのは、嬉しい気が
します。それが初心者にとって、どれだけ嬉しいかはわかりませんが。
>だとすれば、初心者さんにとって、馴染みの薄いLispの勉強と、
>言語処理系の作成というふたつのことを同時に勉強させるのは酷というものでは
>ないでしょうか。
Lispの核となる概念自体はかなりシンプル(だと思う)なので、覚えるべき事柄は
そう多く無い気もします。もちろん、Common LispなりSchemeなりを実装しようと
思うと、勉強しなければならないことがたくさんあるはずですが、処理系作成の
勉強としては、「Lispのようなもの」を作れれば十分ですし。ただ、馴染みが
薄いため、取っ付きが悪いというのはありそうです。
> もっとも、何かを理解しようと思ったら、それを作ってみるのが一番手っ取り早い
>とは思っていまして、Lispを勉強したい人に「じゃあLispを作ってみようよ」という
>提案は有効だと思います。でも、言語処理系を作ってみたい、という人が、
>本当に作りたいのは、Lispなんでしょうか。
たぶん(多くの人にとっては)違うと思います。しかし、とりあえず練習として
作ってみることで、言語処理系の作成に必要な基礎知識が身につくという効果は
あると思います。どうせ、初めて作る言語で、いきなり自分の欲しい言語を作る
なんてのは無理ですし。
>>基本的なアイデアとしては、いわゆる「単純委譲」をコンパイラが自動生成する
>
> 了解です。
> ところで、この形式で、MyListはArrayListなんでしょうか。
> そうだとすれば、多重継承を許そうとすると、「ArrayListインタフェース」のような
>インタフェースをたくさん作らなければならないような気がします。
いえ。あくまで委譲コードの自動生成なので、型としては、ArrayListと
MyListは何ら関係がありません。ですから、型の継承関係を作りたけ
れば、別途クラスを継承するか、インタフェースを実装します。
例えば、MouseListenerとWindowListenerのインタフェースを多重継承して、
それぞれの実装をMouseAdapterとWindowAdapterに委譲するコードは、
class MouseAndWindowAdapter implements MouseListener, WindowListener {
/*MouseListenerインタフェースに対する呼び出しを委譲*/
forward MouseListener mouseListener_ = new MouseAdapter();
/*WindowListenerインタフェースに対する呼び出しを委譲*/
forward WindowListener windowListener_ = new WindowAdapter();
}
と書けます。
>>で、問題になっている環境へのアクセス方法ですが、クロージャから外側の
>>環境にアクセスしていることをコンパイラが検出したら、外側の環境での変数
>>へのアクセスをObject型配列の要素に対するアクセスに変換して、Object型配列
>>への参照を、クロージャに渡すという方法を取ります。
>
> ええと、クロージャが再帰した時はどうなるのでしょうか。Pascalの
>スタティックリンクのような仕掛けがないと困るような気が…
>すみませんよぱらいなので頭動いてないです。
クロージャの中で、自分を再帰的に呼び出したときということでしょうか。
それなら、おそらく問題無いと思います。
例えば、以下のような再帰でフィボナッチ数列を計算するクロージャfibが
あったとして(これだと、クロージャにする意味があまりありませんが)、
interface UnaryIntegerFunction {
int call(int arg);
}
class Hoge{
public static void main(String[] args){
UnaryIntegerFunction fib = #(int n){
if(n == 1 || n == 2){
return 1;
}else{
return fib.call(n - 1) + fib.call(n - 2);
}
};
System.out.println(fib.call(3));
}
}
これは、
class UnaryIntegerFunction$1 implements UnaryIntegerFunction {
private Object[] environment_;
UnaryIntegerFunction(Object[] environment){
environment_ = environment;
}
public int call(int n){
if(n < 2){
return 1;
}else{
return ((UnaryIntegerFunction)environment_[1]).call(n - 1) +
((UnaryIntegerFunction)environment_[1]).call(n - 2);
}
}
}
class Hoge {
public static void main(String[] args){
Object[] environment = new Object[2];
environment[0] = args;
environment[1] = new UnaryIntegerFunction$1(environment);
System.out.println("fib(3) = " +
((UnaryIntegerFunction)environment[1]).call(3));
}
}
というコードに変換されます(先の投稿の例では、引数をenvironment
配列に入れるのを忘れていたので、若干先の例とは変換のされ方が違っています)。
このコードは問題無く動くと思うのですが、何か勘違いしているでしょうか?
あと、Pascalはよく知らないのですが、Pascalのスタティックリンクとは
どのようなものなのでしょうか?
> ちなみにですが、crowbarはJavaScript的に環境ごとに連想配列を持ち、
> 外側環境へのリンクを持とうと思っています。
とすると、変数のルックアップは、ある環境を探索して、見つからなければ
外側の環境へのリンクをたどって…という風に行われるのでしょうか。