K.Maebashi's BBS

ご自由に書き込んでください。雑談も可。
テスト書き込みの類はテスト用掲示板にどうぞ

[日付順表示] [日付順インデックス] [スレッド順インデックス]

新規投稿 | 開設者ホームページへ戻る | ヘルプ

[344] プログラミング言語を作る
投稿者:mizu
2007/02/20 02:13:25

こんにちは。 新企画「プログラミング言語を作る」楽しみにしています。私はよく 友達(もちろん、一応プログラミング経験者の)に、「言語作ろうぜ。 面白いよ」とよく言っているのですが、プログラミング言語を作るというと、 やはり「難しそう」というイメージがあるのか、敬遠されることが 多いです。 こういうページを見て、「俺言語を作ってやろう」という人が増えるなら、 プログラミング言語好きとしては、嬉しいので、頑張ってください。もちろん、 無理をされない程度に。 ------------------------ 以下、ちょっと宣伝です。 実は私も新しいプログラミング言語をJavaCCで作っていまして、近いうちに 公開しようかと考えているところです。 どんな言語かと言いますと、Java VMのバイトコードを吐く、静的型の オブジェクト指向言語で、JavaのOOP機能に、委譲やクロージャなどを備えた言語 です。既にほとんどの機能のコンパイルができる所まで完成していて、今少しずつ ドキュメントを書いているところです。 一応、特色としては、Javaライクな静的型オブジェクト指向言語でありながら、 スクリプト言語のようにトップレベルにスクリプトがかけたり、型宣言が省略可能 な辺りでしょうか。
[この投稿を含むスレッドを表示] [この投稿を削除]
[345] Re:プログラミング言語を作る
投稿者:(ぱ)
2007/02/20 02:13:25

>プログラミング言語を作るというと、 >やはり「難しそう」というイメージがあるのか、敬遠されることが >多いです。 やってみるとそんなに難しくはないんですよね。実際、今回の企画はそれを 示そうと思ってはじめました。時間がなくてなかなか思うに任せていませんが。 # そんなに難しくはない、ということを示したいんだから、あっという間に # 実装できた方が説得力あるんでしょうけどねえ。 >実は私も新しいプログラミング言語をJavaCCで作っていまして、近いうちに >公開しようかと考えているところです。 おお、すばらしい。 >どんな言語かと言いますと、Java VMのバイトコードを吐く、静的型の >オブジェクト指向言語で、JavaのOOP機能に、委譲やクロージャなどを備えた言語 >です。既にほとんどの機能のコンパイルができる所まで完成していて、今少しずつ >ドキュメントを書いているところです。 > >一応、特色としては、Javaライクな静的型オブジェクト指向言語でありながら、 >スクリプト言語のようにトップレベルにスクリプトがかけたり、型宣言が省略可能 >な辺りでしょうか。 ・委譲の言語仕様とか、 ・クロージャの実装方法(環境へのアクセス方法)とか、 ・型宣言を省略する方法とか(静的型とのことなので、レキシカルな最初の代入から推測?)  等に興味があります。  公開される日を楽しみにしています。
[この投稿を含むスレッドを表示] [この投稿を削除]
[346] Re:プログラミング言語を作る
投稿者:mizu
2007/02/20 02:13:25

今回の投稿は、説明の都合上、大分長くなってしまいました。迷惑でしたら、 すいません。 >やってみるとそんなに難しくはないんですよね。実際、今回の企画はそれを >示そうと思ってはじめました。時間がなくてなかなか思うに任せていませんが。 ># そんなに難しくはない、ということを示したいんだから、あっという間に ># 実装できた方が説得力あるんでしょうけどねえ。 簡単に実装するなら、Lisp系言語などが楽でいいのではと思いましたが、 どうでしょうか。普通の言語に比べて、構文がちょっと特殊ですが、機能 拡張がわりと容易ですし、言語設計の面白さは十分に味わうことができるの ではないかと思います。 >・委譲の言語仕様とか、 基本的なアイデアとしては、いわゆる「単純委譲」をコンパイラが自動生成する というもので、Java風に書くと、 class MyList{ forward List list_ = new ArrayList();//委譲宣言 } というコードから、 class MyList{ List list_ = new ArrayList(); public void add(Object o){ list_.add(o); public Object get(index i){ return list_.get(i); } } というコードをコンパイラが生成します。もちろん、これだけだと メソッド名が衝突したときに問題が発生しますが、経験から言って、 そのような衝突は、おそらくさほど頻度は高くないだろうと思われるので、 コンパイルエラーにして、衝突したメソッドのみ、プログラマにどのフィールドに 委譲するか(あるいはそもそも委譲せず、そのメソッドを新たに定義するか)を 選択させれば良いと考えています。 あと、全部のメソッドを委譲するだけでは不便なので、委譲したくないメソッドを 列挙する機能も必要かなと考えていますが、これはまだ実装するかどうかわかり ません。 >・クロージャの実装方法(環境へのアクセス方法)とか、 実は、クロージャの部分はまだ実装できていないのですが、仕様は大体決まって いて、クロージャは、新たなデータ型を導入するのではなく、インタフェース の実装クラスのインスタンスとして、クロージャを生成するという方法を 取ろうと思っています。 ActionListener n = #(ActionEvent event){ System.out.println("actionPerformed"); }; というコード(#以降がクロージャの定義で、eventはクロージャの仮引数です)は、 class ActionListener$1 implements ActionListener { public void actionPerformed(ActionEvent event){ System.out.println("actionPerformed"); } } ... ActionListener n = new ActionListener$1(); というコードにコンパイルされます。 ここでは、ActionListenerがメソッドを1つしか持っていないため、クロージャ の定義では、メソッド名を指定する必要はありませんが、メソッドを複数持った インタフェースの場合、対応するメソッドを指定する構文を用意しようかと 思っています。 で、問題になっている環境へのアクセス方法ですが、クロージャから外側の 環境にアクセスしていることをコンパイラが検出したら、外側の環境での変数 へのアクセスをObject型配列の要素に対するアクセスに変換して、Object型配列 への参照を、クロージャに渡すという方法を取ります。例えば、 class Hoge{ public static void main(String[] args){ int n = 0; ActionListener listener = #(ActionEvent event){ n++; }; listener.actionPerformed(null); System.out.println("n: " + n); } } というコードは、 class ActionListener$1 implements ActionListener { private Object[] environment_; ActionListener(Object[] environment){ environment_ = environment; } public void actionPerformed(ActionEvent event){ environment_[0] = new Integer(((Integer)environment_[0]).intValue() + 1); } } class Hoge{ public static void main(String[] args){ Object[] environment = new Object[1]; ActionListener listener = new ActionListener$1(environment); listener.actionPerformed(null); System.out.println("n: " + ((Integer)environment[0]).intValue()); } } というコードにコンパイルされます。このようなクロージャの実装方式は、 クロージャを新たなデータ型として導入するのに比べて、正直使い勝手は 悪いですが、既存のライブラリを活用するという点では、この方式の方が 良いのではないかと思っています。 >・型宣言を省略する方法とか(静的型とのことなので、レキシカルな最初の代入から推測?) その通りです。例えば、以下の代入があったとして、 n = 1; 代入があった時点でのスコープで、nが宣言されてされていなければ、それは int n = 1; という宣言とみなされます。代入される値の型が参照型の場合も、同じですが、 nullが代入される場合だけやや特殊で、 n = null; は、 Object n = null; とみなされます。この機能があれば、例えば、よくある String line; while((line = reader.readLine()) != null){ .. } というコードが、変数宣言無しで、 while((line = reader.readLine()) != null){ .. } だけで書けるようになります(lineのスコープはwhile文で閉じています)。 以上、長々と説明を書かせていただきましたが、よろしければツッコミや 意見をいただければ幸いです。 > 公開される日を楽しみにしています。 どうも有難うございます。頑張って開発を進めたいと思います。
[この投稿を含むスレッドを表示] [この投稿を削除]
[347] Re:プログラミング言語を作る
投稿者:(ぱ)
2007/02/20 02:13:25

>簡単に実装するなら、Lisp系言語などが楽でいいのではと思いましたが、 >どうでしょうか。普通の言語に比べて、構文がちょっと特殊ですが、機能 >拡張がわりと容易ですし、言語設計の面白さは十分に味わうことができるの >ではないかと思います。  うーん、私はLispはEmacs Lispやxyzzy Lispで遊んだ程度ですので、 Lispそのものについては何も言いませんが(Paul Grahamのエッセイは日本語訳された 範囲ではたぶん全部読んでますし、Lispを悪く言うつもりはないですが)、 いわゆる「普通のプログラマ」に馴染みが薄いというのは確かだと思うわけです。  んで、この辺のスレ http://pc8.2ch.net/test/read.cgi/tech/1106129164/ とか見るにつけ、「Lispなら処理系作るの簡単だよ」というアドバイスがちょくちょく あって、それはそれで嘘ではないと思うのですが、yaccなりJavaCCなりの コンパイラコンパイラが使える環境なら、多少の構文の複雑さは問題にならない はずです。だとすれば、初心者さんにとって、馴染みの薄いLispの勉強と、 言語処理系の作成というふたつのことを同時に勉強させるのは酷というものでは ないでしょうか。  もっとも、何かを理解しようと思ったら、それを作ってみるのが一番手っ取り早い とは思っていまして、Lispを勉強したい人に「じゃあLispを作ってみようよ」という 提案は有効だと思います。でも、言語処理系を作ってみたい、という人が、 本当に作りたいのは、Lispなんでしょうか。 >基本的なアイデアとしては、いわゆる「単純委譲」をコンパイラが自動生成する  了解です。  ところで、この形式で、MyListはArrayListなんでしょうか。  そうだとすれば、多重継承を許そうとすると、「ArrayListインタフェース」のような インタフェースをたくさん作らなければならないような気がします。 >で、問題になっている環境へのアクセス方法ですが、クロージャから外側の >環境にアクセスしていることをコンパイラが検出したら、外側の環境での変数 >へのアクセスをObject型配列の要素に対するアクセスに変換して、Object型配列 >への参照を、クロージャに渡すという方法を取ります。  ええと、クロージャが再帰した時はどうなるのでしょうか。Pascalの スタティックリンクのような仕掛けがないと困るような気が… すみませんよぱらいなので頭動いてないです。  ちなみにですが、crowbarはJavaScript的に環境ごとに連想配列を持ち、 外側環境へのリンクを持とうと思っています。
[この投稿を含むスレッドを表示] [この投稿を削除]
[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的に環境ごとに連想配列を持ち、 > 外側環境へのリンクを持とうと思っています。 とすると、変数のルックアップは、ある環境を探索して、見つからなければ 外側の環境へのリンクをたどって…という風に行われるのでしょうか。
[この投稿を含むスレッドを表示] [この投稿を削除]
[349] Re:プログラミング言語を作る
投稿者:(ぱ)
2007/02/20 02:13:25

>> ええと、クロージャが再帰した時はどうなるのでしょうか。Pascalの >>スタティックリンクのような仕掛けがないと困るような気が… >>すみませんよぱらいなので頭動いてないです。 まず前提として、 ・クロージャはそれ自体が式なので、クロージャの中にも書けるはず。  よって、クロージャはネスト可能。 ・ということは、内側のクロージャは、外側のクロージャの環境と、  さらにその外側(普通の関数)の両方の環境が見えなければならない。 というのがあって、mizuさんの案だと配列をひとつしか渡していないので、 いいのかな、と思ったわけです。  再帰がなければ静的にインデックス計算することも可能でしょうが、 再帰を許すと、インデックスが動的に変化することになりませんか、 というのが先の投稿の意図です。 # 私はクロージャ初心者なので何か勘違いしている可能性はありますが… >あと、Pascalはよく知らないのですが、Pascalのスタティックリンクとは >どのようなものなのでしょうか?  Pascalは関数のネストができるので、内側の関数からは外側の関数の ローカル変数が参照できます。かつ再帰を許すため、現状のスタックの トップからの、外側の関数のローカル変数の位置は静的には決まりません。 そこで、最新の環境に、外周の関数の環境へのポインタを保持していて それをスタティックリンクと呼びます。 # Pascalの処理系を読んだわけではないので、「エキスパートCプログラミング」の # 受け売りですが。 >とすると、変数のルックアップは、ある環境を探索して、見つからなければ >外側の環境へのリンクをたどって…という風に行われるのでしょうか。 そのつもりです。JavaScriptではこんな感じらしいですし。 http://www.hawk.34sp.com/stdpls/jsnotes/jssinso/  ただ、グローバル変数を単なる再外周の環境として扱うか、特別扱いするかは 現在悩んでいるところです。
[この投稿を含むスレッドを表示] [この投稿を削除]
[351] Re:プログラミング言語を作る
投稿者:mizu
2007/02/20 02:13:25

>まず前提として、 > >・クロージャはそれ自体が式なので、クロージャの中にも書けるはず。 > よって、クロージャはネスト可能。 >・ということは、内側のクロージャは、外側のクロージャの環境と、 > さらにその外側(普通の関数)の両方の環境が見えなければならない。 > >というのがあって、mizuさんの案だと配列をひとつしか渡していないので、 >いいのかな、と思ったわけです。 すいません。説明不足でした。前の例で配列を1つしかクロージャに渡して いないのは、あの例ではクロージャがネストしていないため、そうなっている だけで、クロージャのネストが増えるごとに、クロージャに渡される配列の数 は1つずつ増えて行く仕組みになっています。つまり、クロージャがn段ネスト した場合、n個の配列がクロージャに渡されることになります。 > 再帰がなければ静的にインデックス計算することも可能でしょうが、 >再帰を許すと、インデックスが動的に変化することになりませんか、 >というのが先の投稿の意図です。 ># 私はクロージャ初心者なので何か勘違いしている可能性はありますが… クロージャのネスト段数は静的に決定され、かつ、クロージャから 参照されている変数がどのクロージャ(関数)のものであるかは、静的に決定 可能なので、再帰の有無に関わらず、(クロージャから参照される)変数の インデックスは静的に計算できるはずだと思うのですが…。何か勘違いしている のかもしれませんので、もう少し考えてみます。
[この投稿を含むスレッドを表示] [この投稿を削除]
[352] Re:プログラミング言語を作る
投稿者:(ぱ)
2007/02/20 02:13:25

>すいません。説明不足でした。前の例で配列を1つしかクロージャに渡して >いないのは、あの例ではクロージャがネストしていないため、そうなっている >だけで、クロージャのネストが増えるごとに、クロージャに渡される配列の数 >は1つずつ増えて行く仕組みになっています。つまり、クロージャがn段ネスト >した場合、n個の配列がクロージャに渡されることになります。 クロージャがネストするたびに、配列の要素が増えるのではなく、配列が 増えるわけですよね。つまり2次元配列(配列の配列)が渡ることになる、と。 あるいは、ネストが増えるたびに引数の数そのものが増えるのかも しれませんね。 どちらにせよ、それなら納得です。 最近本格的に時間がなくて、断片的な疑問の提示しかできなかったため 余計なお手間を取らせてしまいました。すみません。
[この投稿を含むスレッドを表示] [この投稿を削除]
[354] Re:プログラミング言語を作る
投稿者:mizu
2007/02/20 02:13:25

>クロージャがネストするたびに、配列の要素が増えるのではなく、配列が >増えるわけですよね。つまり2次元配列(配列の配列)が渡ることになる、と。 >あるいは、ネストが増えるたびに引数の数そのものが増えるのかも >しれませんね。 引数の数が増える方を想定していました。2次元配列の方だと、クロージャから 外側の環境の変数にアクセスするたびに2次元配列にアクセスすることになる分、 性能が低下しそうなので。 > >どちらにせよ、それなら納得です。 > >最近本格的に時間がなくて、断片的な疑問の提示しかできなかったため >余計なお手間を取らせてしまいました。すみません。 いえ、こちらこそ、説明が足りなかったせいで余計な手間を取らせてすいません でした。
[この投稿を含むスレッドを表示] [この投稿を削除]