13

久々のヒット。
同僚のid:onishiに「ひげぽんは、古川 日出男 を読むと良いよ。合いそう。」と言われ入門として「13」を勧められました。


序盤の濃密な描写と視覚に訴える感性は良いっすね。
序盤のまともっっぷりから後半のちょっとした崩れ具合が、舞城王太郎をちょっとだけ髣髴とさせます。
次は「アラビアの夜の種族」を読もうと思います。
全読み作家認定かも。

13
13
posted with amazlet on 06.07.30
古川 日出男
角川書店 (2002/01)
売り上げランキング: 45,266

関数型言語の勉強にSICPを読もう - (66) 4章 - 超言語的抽象(222ページ) C++でSchemeインタプリタを作ろう15

問題4.6

letのサポートを入れようという話。
letはlambdaを使って等価変換ができる。
例えば

(let ((a "hello") (b "good bye"))
  (display a) (display b))

((lambda (a b) (display a) (display b)) "hello" "goodbye")

となる。


なのでTranslatorの時点で let式を見つけたら、Letオブジェクト生成する。
で、Letのeval時に Lambda式に変換してから evalする。
こんな感じ。

Application* Let::expand()
{
    Lambda* lambda = new Lambda(body_, variables_);
    return new Application(lambda, values_);
}

Object* Let::eval(Environment* env)
{
    Object* application = expand();
    return application->eval(env);
}

問題4.7

let*の話。

(let* ((a 3) (b (+ a 3)) (c (- b 1)))
  (display c))

let* では左から順に評価されて、左側の変数を参照できる。
これを let で表現して let*をインタプリタでサポートせよという問題。


let*は let の入れ子でかけると思う。

(let ((a 3))
  (let ((b (+ a 3)))
    (let ((c (- b 1)))
      (display c))))

こんな感じで。
Let* を Letに変換する部分を書いて(もちろん再帰)、きちんと動いた。
なんだかマクロを実装した方が早いんじゃないかという気もしなくもない。
でもまあSICPではマクロが出てこないから微妙。



※「SICPを読もう」の目次はこちら


計算機プログラムの構造と解釈
ジェラルド・ジェイ サスマン ジュリー サスマン ハロルド エイブルソン 和田 英一
ピアソンエデュケーション (2000/02)
売り上げランキング: 56,404

関数型言語の勉強にSICPを読もう - (66) 4章 - 超言語的抽象(222ページ) C++でSchemeインタプリタを作ろう15

いろいろな構文が動いたので悪ノリしてパフォーマンスを測定してみたら恐ろしい罠が!

計測対象

(begin
  (define count (lambda (x)
                  (if (= 5000 x)
                      x
                      (begin
                        (count (+ x 1))))))
  (display (count 0)))

条件分岐・変数の参照・再帰・値の比較などなどの要素を盛り込んであります。

結果

Gaucheでは一瞬で実行が終わるのに、数十秒かかる。。。

どうして遅いのか?

ループの回数を増やすと極端に遅くなるということから、変数のlookupのあたりが怪しいなぁとあたりをつけます。
で、gprof を使って見たところ。

  %   cumulative   self              self     total
 time   seconds   seconds    calls  us/call  us/call  name
 49.79      4.65     4.65 37537506     0.12     0.21  monash::Frame::lookup(monash::Variable*)

やはりlookupに時間がかかっていますね。ん?callの回数が37,537,506 回!


今のlookupは FrameのリストからFrameを取り出してhashmapで変数を名前からlookupします。
そのFrameに変数が見つからなければ、次のFrameに対して同じことをやります。
例えば + をlookupした場合、最上位のFrameに束縛されているので、Frameのリストを最後までたどってやっと変数が見つかります。


で、問題は5000回目のループの時点でFrameのリストの要素が5000個あって、更に最悪の場合その全ての要素に対してlookupするという状況が発生するということです。


再帰呼び出しで、再帰の回数分Frameができてしまうのがしょうがないならば、変数のlookup処理を工夫すれば良さそうです。
例えば、グローバルなhashmapにlookupテーブルを作っておけばかなり速くなると思う。
再帰の回数分Frameができるってのは結局 inlineで展開しているようなもので何かおかしい気がするけどもう少し考えてみます。

竹内関数

コメントにて竹内関数というものを教えていただいた。
どうもこの関数はベンチマーク関数として有名らしい。

(begin
  (define tak (lambda (x y z)
    (cond
     ((> x y) (tak (tak (- x 1) y z) (tak (- y 1) z x) (tak (- z 1) x y))) (#t y)))) (tak 12 6 0))

一瞬で終わった。これは速かった。
ん?でも tak の戻り値がGacheの結果と違うなぁ。

※「SICPを読もう」の目次はこちら


計算機プログラムの構造と解釈
ジェラルド・ジェイ サスマン ジュリー サスマン ハロルド エイブルソン 和田 英一
ピアソンエデュケーション (2000/02)
売り上げランキング: 56,404