letrec と let*
一ヶ月以上前の話に今更反応するエントリ → http://mywiki.jp/itkz/SICP+%93Ǐ%91%8D%C0%92k%89%EF/2008-02-08/
R5RSでは内部 define の評価順は不定です(letrec と等価とされています)。で、unkoの場合は let* を使うとうまくいきます。let* は上から順に評価して束縛していきます。
letrec は局所的に使う再帰関数を作るためのものなので、内部 define も同様に関数定義に使うものと考えるのが良いっぽい。
letrec と let* (と let) の違いを unkoの挙動で見てみるとこんな感じです。
;; 内部 define 相当 (define (unko-letrec a b c) ;; まず bigger と biggest が未定義値で初期化される(仮想的に) (letrec ((bigger (if (> a b) a b)) ; bigger と biggest はどっちが先に評価されるかわからない (biggest (if (> bigger c) bigger c))) ; ここで bigger は定数かもしれないし、未定義値かもしれない (+ (square bigger) (square biggest)))) ; → うまく動くかもしれないし、エラーかもしれない *** ERROR: real number required: #<undef> ;; うまくいくバージョン (define (unko-let* a b c) ;; let* は上から順に評価して束縛していく (let* ((bigger (if (> a b) a b)) ; bigger も biggest も存在しない環境に変数 bigger を導入 (biggest (if (> bigger c) bigger c))) ; ここでは bigger は存在していて、値は (if (> a b) a b) の結果 (+ (square bigger) (square biggest)))) ;; うまくいかないバージョン (define (unko-let a b c) ;; let の場合は全部外側の環境で評価される (let ((bigger (if (> a b) a b)) ; bigger も biggest も未定義(変数自体が存在しない) (biggest (if (> bigger c) bigger c))) ; bigger も biggest も未定義(上に同じ) (+ (square bigger) (square biggest)))) ; → *** ERROR: unbound variable: bigger