海野秀之(うんのひでゆき)の外部記憶
Twitter (twilog) / RSS / アンテナ / ぶくま
「勉強する前に書け!」 ということで、 Scheme もどき処理系を書いてみた。
きちんとした処理系を書くだけの能力がないことは自覚していて、 だからこそ、ぼちぼち勉強しているつもりなのですが。 じゃあ「きちんとしてないやつなら書けるのか?」というと ……うーん、書いたことがないなぁ。
これは、まずい。というわけで、書いてみた。
なるべく手を抜けるところは手を抜いて、小さいコードにしたかったんだけど、 500 行は切れなかったよ:
% wc -l iscm0.[hyl] main.c eval.c 36 iscm0.h 20 iscm0.l 28 iscm0.y 176 main.c 390 eval.c 650 合計
iscm というのは、integer くらいしかサポートしていない スキーム(笑)くらいのつもりです。 国際現代音楽協会とは関係ないと思います。
いちおう、動作サンプルは以下のような感じ:
(display "Hello!\n") (define x 2) (define y 3) (display (+ x y)) (display "\n") (define f (lambda (x y) (define pow (lambda (x) (* x x))) (+ (pow x) (pow y)))) (display (f x y)) (display "\n") (set! x 4) (display (f x y)) (display "\n")
を実行すると:
% ./iscm0 < sample1.scm Hello! 5 13 25
となります。
(define mkf (lambda () (define ct 0) (lambda () (set! ct (+ ct 1)) (display ct) (display "\n")))) (define gg (mkf)) (define g (mkf)) (g) (g) (g) (gg) (g) (gg)
なら、
% ./iscm0 < sample2.scm 1 2 3 1 4 2
です。いわゆるクロージャってやつ?
(define f (lambda (x) (define f_loop (lambda (x result) (cond ((= x 1) result) (else (f_loop (- x 1) (* result x)))))) (f_loop x 1))) (display (f 2)) (display "\n") (display (f 3)) (display "\n") (display (f 5)) (display "\n")
のような、再帰関数も一応動く:
% ./iscm0 < sample3.scm 2 6 120
ほかにも、書いているときにはいろいろ思ったような気がするんだけど。 思い出したら書き足そう。
とにかく、いまの僕に書けるものを、なるべく思い込みに基づいて書きました。 「Lisp の必須要素は、えーと、なんだっけ?」というのも調べずに書く(笑)! 動くものをつくってから、どこがダメなのかを理解していこうと。
これを触りつつ SICP を読んでいくと、よく解りそうだ!
今後は、これを徐々にまともにしていこう。 iscm8 くらいで、見られるものになる筈(←言うだけならタダ!)。 それに、ほんとは、インタプリタじゃなくて、コンパイラが書きたいんだよなぁ。
*1 「メモリリーク」と呼ぶ人もあるでしょう。
できたてほやほやの iscm0 を使いつつ、SICP の練習問題でもやってみるかなと思っ……て……、 あら?
整数の四則演算 (+, -, *, /) と比較演算 (=) は、built-in 関数として実装したのに、 不等号をわすれた!!
ぎぃやぁん!
しばらく遊べるかなと思ったら、いきなりのバグフィックス。
不等号も欲しいねというのがいっこ。
もういっこは、スコープが変で、 関数のなかからグローバル?(一番外の)環境が見えなかったバグを直した。 ちぃ、これは、わかってたのに、いろいろ書いている途中で劣化してしまったもの。 スコープの理解が変で、実装がまちがっているところは、あるんじゃないかと思っているんだが、 今回のは、単なるミスです。くやしい。
というわけで、SICP の練習問題 1.3 はこんな感じでしょうか:
ex1_3.scm:
(define f (lambda (a b c) (define g (lambda (x y) (+ (* x x) (* y y)))) (cond ((and (<= a b) (<= a c)) (g b c)) ((and (<= b a) (<= b c)) (g a c)) (else (g a b))))) (display (f 1 2 3)) (display "\n") (display (f 4 2 3)) (display "\n") (display (f 4 5 3)) (display "\n")
iscm0 (version 0.0.1) で動かすには、さらに、
lib4iscm0.scm:
(define and (lambda (a b) (cond (a b) (else ())))) (define not (lambda (a) (cond (a ()) (else 1)))) (define <= (lambda (a b) (not (> a b))))
が必要。
実行結果:
% cat lib4iscm0.scm ex1_3.scm | iscm0 13 25 41
lambda の仮引数部の形式を一種類しかサポートしてなくて、 引数を可変個数にできないのが、いきなり目障りになってきた(and が二項しかとれない!)。
(追記:and については、二項の場合しかサポートしていないことよりも、 致命的な問題があります。and のセマンティクスを実現するには、 and は特殊形式である必要があります(筈)。or も同様。SICP 問題 1.6 近辺)
おっと、そうそう。論理については、() が偽、それ以外が真という体系になってます。
Unnamed union がお行儀わるくて、 gcc でもバージョンによってはコンパイルできないようだった。 他にも、いくつか警告メッセージを頼りに修正した。
やはり、警告メッセージを無視しちゃいけませんでした。
>and は特殊形式<br><br>遅延評価をデフォルトにすれば。
でたな、OCamler!!<br><br>たしかに、その通りで、言葉ったらずでした。<br><br>僕は Scheme もどきから始めましたが、<br>最終的には「ちゃんとした」 Scheme にしたいので、俺言語的拡張はしない方向で。