今日の要約: 俺Lispに必要な仕様がまとまってきた。実際に作るかどうかは未定。しかし、カッコ良い名前を考えておく事。

等と考えていた。
eval/svも作った。
で、cut-seaさんの http://practical-scheme.net/wiliki/wiliki.cgi?cut-sea#H-8uph22 を見て、Paul Grahamが「必要としている」プログラミング言語がどのようなものなのかを思い知った。


では、自分が「必要としている」プログラミング言語は、どんな存在なのか?
これについては、一応結論は出ている。
「コードジェネレータ(プログラム)にとって、コードを書きやすい言語」だ。
(しかしながら、大抵の言語は、「人間にとって、コードを書きやすい」事を目的(または副目的)とされているように思える(そうでないのは各cpu用の機械語ぐらいか?)。Paul Grahamの言う「パワーのある」(言語)という言葉も、「人間にとって、コードを書きやすい」という意味のように思える。)
(ついでに言うと、「コードジェネレータにとってコードを書きやすい言語」が「人間にとってもコードを(比較的)書きやすい言語」になる可能性もそれなりにある。勿論、それは主目的ではないが。)


じゃあ、その言語はどのような特徴を持っているべきなのか?
これについても、ある程度は結論が出ている。
で、それに追加して、この前にもう少し考えてみた内容も足して、リストアップしてみる。


ほぼ確定の項目:

  1. 「人間の歴史」や「歴史的理由」等に由来した、無意味なルールは持ち込まない事(例えば、数値演算記号の優先順位など)。
  2. 動的言語である事。より具体的には、動的にevalが実行できる事。
    • 型については、動的型にするか静的型にするかは、もっと熟考する必要がある。しかし、なんか考えてるだけでは結論は出ない気がするので、もし実装が必要な時は、仮に「動的型」で作る、という事にしておく。勿論、余裕があるなら両バージョン作る事(しかし、そんな余裕があるなら他の事をすべきだ)。
  3. 基本的には、evalが「安全」である事を保証する事(勿論、わざわざ自分の足を撃つ行為を完全に禁止させるレベルの「安全」は不要)。具体的には、以下の二つを保証する。
    1. 無限ループ対策として、evalを途中で中断する機能を持つ事(eval/sv的な機能)。
      • eval/svでは、evalの挙動自体を制御可能だが、そこまでの機能が必要かどうかは不明。しかしとりあえず、無限ループ対策は必須。
    2. リソース消費過多対策として、eval内で利用できるリソースを制限可能とする事(eval/svでは提供されていない)。
      • これはregion方式のメモリ管理を参考にできる。最初にmalloc(的な何か)で、eval内で利用できる領域をいきなり確保し、eval内でcons等によってメモリを使う時はその領域の中から使っていく。もし領域内の残りメモリが無くなったら、そのevalはエラー例外を投げて終了する。使われなくなったメモリの回収自体は、GCに任せる。evalを抜けたら、eval内で生成されたオブジェクト等は、eval元の親が所有する扱いになる(元々、eval元の親のメモリプールから確保された領域なので)。
      • 勿論、eval内で入れ子的にevalする時は、この使用可能メモリ領域自体も、入れ子構造になる(つまり、eval元のメモリプール内から確保する事になる)必要がある事に注意。
      • これ、malloc(的な何か)で確保した領域(メモリ一次元の特定線分)=eval時に使用していいメモリ量という前提になってるが、何度も再利用してる内にメモリの断片化が発生したら、色々と問題が出ると思う(空きメモリはあってもメモリ断片化によってregionとして確保できないのでevalできない、とか)。「誤差」という事にしてもいい気はするものの……。
  4. (前述の項目と重複するが)プロセス(というかeval)自身のリソースの現在使用量及び最大使用可能量を知る手段を、言語レベルで保証する事。
    • これがあれば、基本的には、mallocが失敗するような事は考えなくてもよくなる筈……。
    • 問題は、これがあったとしても、一回のconsでどれだけ消費するか等も言語レベルで規定されていないと、厳密な操作はできない事。でも、仮に大雑把だとしても、これがあるのとないのとでは大違いだと思う。
  5. 表向きの構文(以下、外部表現と呼ぶ)は、S式またはS式類似の書式とする。
    • 実は、S式はまだ「人間向けの表現」である。コードジェネレータにとってのネイティブ言語は、「S式をreadした後の内部表現」になる。
    • しかし、「じゃあ、その内部表現をバイトコードにでもすべきなの?」となるが、考えた結果、そうはしない事にした。
      1. まず「S式をreadしたら内部表現になり、evalに渡すのはその内部表現」という事になる。これ単体では別に問題ない。
      2. 次に「じゃあevalにはread以外の手段で生成された内部表現を渡してもいいのか」という事になる。これもおそらくは問題ない。
      3. 最後に「じゃあ、evalに渡せる内部表現こそがコードジェネレータにとってのネイティブ言語なんだから、それをS式として書き表わせないと、等価とは言えないんじゃないの?」という事になる。いくらwrite/read invarianceを保てるようにすればいいと言われても、無名pipeのport等のどうしようもないものもある。どうする?
      4. しかし、よく考えたら、この問題は、S式をバイトコードにしても、解決できない事には変わりはなかった(結局ファイルには書き出せない)。表現のレベル的にはS式でもバイトコードでも同じレベルだと思われるので、とりあえずここはS式にしておく事にする。
    • 但し、前述の「内部表現」と「外部表現」の問題はかなり本質的だと思われるので、もっとよく考える必要がある。
  6. Arc的かつニーモニック的な、英単語を連想させるが英単語ではない、略語的ベース命令セット名の採用。
    • これは「コードジェネレータにとってコードを書きやすい言語」には全く影響しないが、自分が「コードジェネレータにとってコードを書きやすい言語」を開発しているんだ、という気分を維持するのに役立つ、かも知れない。
    • 個人が各自で定義したりする時の手続き名等は、特に命名ルール等は定めない事にする。自由。
  7. 可能な限り、プログラマ(=コードジェネレータ)のキャパシティが低くても使いこなせるように言語を設計する事。
    • 具体的に、どのように設計するかは、以下の「まだ不確定な項目」にて考える。


まだ不確定な項目:

  1. 他の項目を妨害しない限りは、なるべくプログラムコードの要素数が少なくなるように言語を設計する(Arcの真似)。
    • プログラムコードの要素数が少なくなる事は、おそらくプログラマ(=コードジェネレータ)に対する負担の軽減になると思われる。
    • 反論: 本当にそうなの?
  2. ベース命令の種類は少ない方が良い。
    • ベース命令の種類が多くなるほど、プログラマが憶えるべき命令数が多くなる。これはおそらくプログラマに対する負担になると思われる。
    • 反論: 「各状況に対してどの命令を使うか」の選択肢の数は実は重要ではないかも知れない。重要なのは「各状況」の数の方かも知れない。
  3. ベース命令の機能は少ない方が良い。
    • 例として、Arcの「if」を考えてみる。
    • Arcのifは、「(if a b c d e)」のような書き方が出来る。これは、aが真ならbを返し、cが真ならdを返し、そうでないならeを返す。
    • これは要するに、「(if a b (if c d e))」の省略形であるとも見れる。
    • Arcは「可能な限りプログラムコードの要素数が少なくなるように言語を設計する」という事なので、このような省略形が用いられているのだろうが、プログラマが、ifの余計な引数に対する知識を憶える事とトレードで、要素数の少なさを得ているように思える。
    • よって、この言語では、「(if a b c d e)」よりも「(if a b (if c d e))」の方をベターな形として採用する。
    • 反論: そうすると代わりに「入れ子にする知恵」が必要になるが、どっちの方がプログラマの負担が軽いのかは謎では?
  4. 構文糖は必要か?
    • 例えば、「(if a b c)」を、「(if a then b else c)」のように、thenとelseという構文糖を付けて記述する事を考えてみる。これはプログラマの負担を増やすのか、それとも減らすのか?
    • 英語を解する人間にとっては、後者の方が読みやすい(但し、読みやすい事が書きやすいかどうかは別問題)。しかし、英語を解さないコードジェネレータにとっては???
  5. first-classなobjectは、consセルとnilのみ。数値、文字、文字列、その他は全て外部objectとして扱う。
    • これによって、プログラマの負担は大きく減る……筈。
    • 手続きやspecial form(やmacro)は?数値演算は外部要素にしてしまえると思うものの、ifやlambda等までそうするのは無理じゃない?


ここまで考えたら、Paul Grahamなら「でも、実際に作ってみて動かしてみなければ、本当にこれが理想なのかどうかは分からない。仕方が無いからさっさと作ろう」となりそうな気がする。
で、そうするなら、Paul GrahamはMzScheme上でArcを書いたように、自分も、何か他の言語上でこれを書くのがいいだろう。
そうなると、既にeval/svを作ったGaucheがおそらくは最適だ。
しかし、上の条件の「malloc的操作」がGaucheには無い。
というか、この機能を持つような、resource sensitiveな動的言語なんてあるのか?
無いなら、自分で一から作るしかないように思えるんだが……。


そして、作る作らないに関わらず、名前が無いと呼び名に困るので、カッコ良い名前を考えておく事。
そして、名前を決めたら、自分のwilikiにページでも作り、仕様をまとめておく事。



追記:
「まだ不確定な項目」辺りを追記した。