2010年5月12日水曜日

Small-Lisp 実装を通して

感想文です。

まあ、前言った通り、

「Lispの実装をLispで行う?ネタ?それってオモロイのか?」

とか懐疑的だったんですけど。実際やってみると面白い(笑)。ハマりました(笑)。色んな意味ですけどね。

まあ、別に自分で0から考えて、ってわけじゃなくって、森北から出ているSchemeによる記号処理入門を見ながらやってたわけですけど。それで自分が気に入らないコーディングスタイルとか、あるいはこうだったら良いなあ、ってのを考えながら弄ってたわけですけれども。

でもマジに意外と面白かったです。ちょっとはSICPの第4章辺り読む自信付いたかな?

個人的には「こりゃ大変だ」とか思ったのがラムダ式(Small-Lispではfnと言う名前になってる)と=(Common Lispで言うsetf)の実装ですかね。あ、ifも大変だったな。中でもやっぱ一番大変だったのは=の実装ですかね。CLのsetfみたいな動作させたい、ってんで、CLでmacroexpandかけて調べてみたんですが、これが出てくるのがprimitiveであるrplacarplacdで。あんまこの二つ使った事が無いんで、Common Lisp 入門 (岩波コンピュータサイエンス)調べながら…と一番手間がかかりました。
結局、大域環境である*environment*をPLTのハッシュで実装してたんで、そこの内容をhash-set!で無理矢理書き換える、と。そう言う荒業を行って何とか解決。alistだったらこう上手くは行かなかったんだろうな。ハッシュテーブル様々です。

まあ、課題が多い実装なんですが。今思いつくトコをちょっとメモ。

  1. ラムダ式のbodyに複数の式が置けない。

  2. レキシカル・クロージャはどうやって実装する?

  3. 一々quoteとか書きたくないんだけど、ショートカットである'の実装方法は?

  4. マクロの実装方法は?


ってなトコですかね。
本の進み方によると、これはダイナミック・スコープにしかなり得ないと思うんで、そこの回避が謎ですね。まあ、この辺はSICP再挑戦して読んでみればわかるのかな?
quoteもみっともないですね。リーダマクロを実装すれば何とかなるのでしょうか。でもそこへ行くのが大変そう(笑)。
最後のマクロ実装。これは痛感しました。実は何を置いてもマクロを最初に実装しておくべきなのでは、と。
例えば、Small-Lispだとdefが特殊オペレータ扱いになってるんですよね。ところが、=fnがあるんで、マクロさえあればその上に被せたレイヤー上に簡単に実装出来るわけです。マクロが無いからevalを直接弄って特殊オペレータとして実装しなきゃならない。これは無駄ですよね。
fnを実装している最中に「あ、これはマクロ書いてるっぽいな」とかちょっと思ったわけですけど、要するに直接evalを弄らずにショートカットできればいいのに。マクロは偉大です。自分でLispを実装してみるのがマクロ理解への一つの方策なのかもしれません。
が、マクロ実装を解説してあるような本ってあるんか(笑)。

その辺でcond実装するのも止めちゃったんだよね(笑)。マクロとifがあればcondなんてお茶の子さいさいで実装出来るのに。スカンタコ。

でもまあ、マジで面白いですよ。自分でプログラミング言語を曲りなりにも実装する、ってのは自信に繋がりますね。面白い。んで、ポール・グレアムのArcのコードも参考にしてたんですが、手探りでやってても色んな事が徐々に判ってくる。

ところで、CSの宿題で「LispでLispを実装しなきゃいけない」って言われて嫌々やってる人たちにちょっとヒントを。

applyevalの仕事の区別なんて付けないで良い


いきなり怒られそうな事を書いてるんですけど(笑)。でも、やってみてここはそう思いました。
SICPなんかでは「Lispはevalapplyの相互作用」とかエラソーな事書かれてたんですけど(笑)。実際、この辺は実装者のさじ加減でしょうね。
つまり、やりたけりゃ、特殊形式であるconsとか、特殊形式であるcarとかやってもいいわけです。「やっちゃいけない」ってワケじゃない。まあ、Lispのオーソドックスな実装上の流れから言ってevalapplyに分けてる、って考えて良いでしょう。
一般には、次の役割がそれぞれあるわけですよ。

  1. 特殊形式はeval内で実装する。

  2. 基本関数はapply内で実装する。


これは要するに、REPLで打った時、引数にクオートが要らないのが前者、引数にクオートが必要になるのが後者、です。それだけ、なんです(笑)。マジな話で(笑)。
つまり、REPLで打った時に「引数いらねえ方が俺は好きだな」って場合は、堂々とeval内で実装しちゃいましょう(笑)。貴方のLispである以上、貴方の好みで良い筈です。それが例え「スチャラカな」実装だとしても。
実装形式上の話をすると、evalexpressionを丸ごと一つとして受け取りますが、codeは分解された状態(function部とその引数部)で
expression
を受け取ります。このささいな違いがREPL上の引数の動作に影響を与える、のです。

メンド臭い実装はベースのLispに丸投げしちゃおう(笑)



これもそうですね。いくら「自分で実装する」とか言っても、基本的な関数で実装がメンド臭そうだったら、レイヤーの下で動いてるLispに丸投げするのも手、です。全部自分で造らなくても良い。もし、それが必要なら、アセンブリで実装する以上の事ってないわけですから。せっかくLispでLispを実装するんなら、余計な部分は下部で動いているLispに丸投げするのも手、です。恥ずかしがる必要はありません。
なお、ポール・グレアムのArcも読みながらやってたんですが、ポール・グレアムも面倒な部分は下部のPLTに丸投げしてました(笑)。Arcのソースコード内で逆引用符が使われているリストの部分は、どうやら下部のPLTに丸投げする為のハックの模様です。

caseを使いこなそう


どうも、あまりにもcondが優秀なのと、ミニマリズムのせいで、ついついSchemeでは存在が薄れがちなcaseですけど。ソースコードを短く、かつ、読みやすくする為にはcaseの多用が必要だ、と感じました。evalapplyもコードが大きく成りかねないんで、それを避ける為には、

  1. 手続きの分割

  2. caseの多用


が肝です。
そもそも、Emacsの画面の半分以上を占めるようなコードを書くべきじゃない、と言う気がします。ショートカットになる為だったら色々と試してみるべきでしょう。同様に部分解が簡単に形成出来るんだったらdefine-syntax等のマクロも多用すべきだと思います。
Small-Lispだと、しょーもないマクロを3つ程作りましたが、それにより、コードの短縮化が可能になったんで、まあ良し、と思っています(もうちょっと上手く抽象化出来るかもしれない、って疑念もありますが)。

とまあ、感想文でした。
この続きはSICPに進むか、あるいはArcのソースをCLに移植するか。ちょっと楽しくなってきました。

0 件のコメント:

コメントを投稿