まあ、前言った通り、
「Lispの実装をLispで行う?ネタ?それってオモロイのか?」
とか懐疑的だったんですけど。実際やってみると面白い(笑)。ハマりました(笑)。色んな意味ですけどね。
まあ、別に自分で0から考えて、ってわけじゃなくって、森北から出ているSchemeによる記号処理入門を見ながらやってたわけですけど。それで自分が気に入らないコーディングスタイルとか、あるいはこうだったら良いなあ、ってのを考えながら弄ってたわけですけれども。
でもマジに意外と面白かったです。ちょっとはSICPの第4章辺り読む自信付いたかな?
個人的には「こりゃ大変だ」とか思ったのがラムダ式(Small-Lispでは
fn
と言う名前になってる)と=
(Common Lispで言うsetf
)の実装ですかね。あ、if
も大変だったな。中でもやっぱ一番大変だったのは=
の実装ですかね。CLのsetf
みたいな動作させたい、ってんで、CLでmacroexpand
かけて調べてみたんですが、これが出てくるのがprimitiveであるrplaca
とrplacd
で。あんまこの二つ使った事が無いんで、Common Lisp 入門 (岩波コンピュータサイエンス)調べながら…と一番手間がかかりました。結局、大域環境である
*environment*
をPLTのハッシュで実装してたんで、そこの内容をhash-set!
で無理矢理書き換える、と。そう言う荒業を行って何とか解決。alistだったらこう上手くは行かなかったんだろうな。ハッシュテーブル様々です。まあ、課題が多い実装なんですが。今思いつくトコをちょっとメモ。
- ラムダ式の
body
に複数の式が置けない。 - レキシカル・クロージャはどうやって実装する?
- 一々
quote
とか書きたくないんだけど、ショートカットである'
の実装方法は? - マクロの実装方法は?
ってなトコですかね。
本の進み方によると、これはダイナミック・スコープにしかなり得ないと思うんで、そこの回避が謎ですね。まあ、この辺はSICP再挑戦して読んでみればわかるのかな?
quote
もみっともないですね。リーダマクロを実装すれば何とかなるのでしょうか。でもそこへ行くのが大変そう(笑)。最後のマクロ実装。これは痛感しました。実は何を置いてもマクロを最初に実装しておくべきなのでは、と。
例えば、Small-Lispだと
def
が特殊オペレータ扱いになってるんですよね。ところが、=
とfn
があるんで、マクロさえあればその上に被せたレイヤー上に簡単に実装出来るわけです。マクロが無いからeval
を直接弄って特殊オペレータとして実装しなきゃならない。これは無駄ですよね。fn
を実装している最中に「あ、これはマクロ書いてるっぽいな」とかちょっと思ったわけですけど、要するに直接eval
を弄らずにショートカットできればいいのに。マクロは偉大です。自分でLispを実装してみるのがマクロ理解への一つの方策なのかもしれません。が、マクロ実装を解説してあるような本ってあるんか(笑)。
その辺で
cond
実装するのも止めちゃったんだよね(笑)。マクロとif
があればcond
なんてお茶の子さいさいで実装出来るのに。スカンタコ。でもまあ、マジで面白いですよ。自分でプログラミング言語を曲りなりにも実装する、ってのは自信に繋がりますね。面白い。んで、ポール・グレアムのArcのコードも参考にしてたんですが、手探りでやってても色んな事が徐々に判ってくる。
ところで、CSの宿題で「LispでLispを実装しなきゃいけない」って言われて嫌々やってる人たちにちょっとヒントを。
apply
とeval
の仕事の区別なんて付けないで良い
いきなり怒られそうな事を書いてるんですけど(笑)。でも、やってみてここはそう思いました。
SICPなんかでは「Lispは
eval
とapplyの相互作用」とかエラソーな事書かれてたんですけど(笑)。実際、この辺は実装者のさじ加減でしょうね。
つまり、やりたけりゃ、特殊形式であるcons
とか、特殊形式であるcar
とかやってもいいわけです。「やっちゃいけない」ってワケじゃない。まあ、Lispのオーソドックスな実装上の流れから言ってeval
とapply
に分けてる、って考えて良いでしょう。
一般には、次の役割がそれぞれあるわけですよ。
- 特殊形式は
eval
内で実装する。
- 基本関数は
apply
内で実装する。
これは要するに、REPLで打った時、引数にクオートが要らないのが前者、引数にクオートが必要になるのが後者、です。それだけ、なんです(笑)。マジな話で(笑)。
つまり、REPLで打った時に「引数いらねえ方が俺は好きだな」って場合は、堂々とeval
内で実装しちゃいましょう(笑)。貴方のLispである以上、貴方の好みで良い筈です。それが例え「スチャラカな」実装だとしても。
実装形式上の話をすると、eval
はexpression
を丸ごと一つとして受け取りますが、code
は分解された状態(function
部とその引数部)で
expression
を受け取ります。このささいな違いがREPL上の引数の動作に影響を与える、のです。メンド臭い実装はベースのLispに丸投げしちゃおう(笑)
これもそうですね。いくら「自分で実装する」とか言っても、基本的な関数で実装がメンド臭そうだったら、レイヤーの下で動いてるLispに丸投げするのも手、です。全部自分で造らなくても良い。もし、それが必要なら、アセンブリで実装する以上の事ってないわけですから。せっかくLispでLispを実装するんなら、余計な部分は下部で動いているLispに丸投げするのも手、です。恥ずかしがる必要はありません。
なお、ポール・グレアムのArcも読みながらやってたんですが、ポール・グレアムも面倒な部分は下部のPLTに丸投げしてました(笑)。Arcのソースコード内で逆引用符が使われているリストの部分は、どうやら下部のPLTに丸投げする為のハックの模様です。
case
を使いこなそう
どうも、あまりにも
cond
が優秀なのと、ミニマリズムのせいで、ついついSchemeでは存在が薄れがちなcase
ですけど。ソースコードを短く、かつ、読みやすくする為にはcase
の多用が必要だ、と感じました。eval
もapply
もコードが大きく成りかねないんで、それを避ける為には、- 手続きの分割
case
の多用
が肝です。
そもそも、Emacsの画面の半分以上を占めるようなコードを書くべきじゃない、と言う気がします。ショートカットになる為だったら色々と試してみるべきでしょう。同様に部分解が簡単に形成出来るんだったら
define-syntax
等のマクロも多用すべきだと思います。Small-Lispだと、しょーもないマクロを3つ程作りましたが、それにより、コードの短縮化が可能になったんで、まあ良し、と思っています(もうちょっと上手く抽象化出来るかもしれない、って疑念もありますが)。
とまあ、感想文でした。
この続きはSICPに進むか、あるいはArcのソースをCLに移植するか。ちょっと楽しくなってきました。
0 件のコメント:
コメントを投稿