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