For Development HEAD DRAFTSearch (procedure/syntax/module):

4.6 変数束縛

Special Form: let ((var expr) …) body …
Special Form: let* ((var expr) …) body …
Special Form: letrec ((var expr) …) body …
Special Form: letrec* ((var expr) …) body …

[R7RS base] 各変数varexprの値に束縛されているローカルな環境を作成し、 その中でbody …を評価します。varはシンボルでなければ ならず、重複があってはなりません。body …の最後の式の値が このフォームの値となります。

これらの4つのフォームの違いは、exprが評価される時点のスコープと順序にあります。 letexprletフォームに入る前の環境において評価します。 各exprの評価される順序は不定で、コンパイラは最適化のために自由に順序を 変更することがあります。 一方、let*exprを現れた順に、 それ以前のvarが束縛された環境において評価してゆきます。

letrecは全てのvarが仮想的に不定の値に束縛された環境において 各exprを(順不同で)評価します。 letrecは相互再帰的なローカル関数を定義する場合に必要です。 最後に、letrec*letrecと同じスコープ規則を使い、 さらにexprを現れる順に評価するものです。

(define x 'top-x)

(let  ((x 3) (y x)) (cons x y)) ⇒ (3 . top-x)
(let* ((x 3) (y x)) (cons x y)) ⇒ (3 . 3)

(let ((cons (lambda (a b) (+ a b)))
      (list (lambda (a b) (cons a (cons b 0)))))
  (list 1 2))  ⇒ (1 2 . 0)

(letrec ((cons (lambda (a b) (+ a b)))
         (list (lambda (a b) (cons a (cons b 0)))))
  (list 1 2))  ⇒ 3

ひとつのexprを評価するために、それまでに現れたvarの値を参照する 必要がある時は、letrec*を使わなければなりません。 下の例では、abの値を計算するためにcubeの値を 使っているので、letrec*にする必要があります。 (上の例との違いに注意してください。上の例では、listの値を計算する 時には、同時に束縛されているconsの値は必要とされません。consの値が 必要になるのはlistが実際に適用される時です。)

(letrec* ((cube (lambda (x) (* x x x)))
          (a (+ (cube 1) (cube 12)))
          (b (+ (cube 9) (cube 10))))
  (= a b)) ⇒ #t

この例は現在のGaucheではletrecを使ってもたまたま動作しますが、 将来にわたって動作し続けることは保証されません。 letrecを使うなら評価順に依存しないように、プログラマが気をつけないといけません。 振り返ってみれば、letrec*だけが提供されてた方が単純だったでしょう。 生憎、Schemeの歴史の中ではletrecの方がずっと前からあったので、いまさら 取り除くこともできないのです。また、letrec*に出来ない最適化が letrecだと可能になることもあります。

Macro: let1 var expr body …

変数が一つしか無い場合の便利なマクロです。次のように展開されます。

(let ((var expr)) body ...)
Macro: if-let1 var expr then
Macro: if-let1 var expr then else

このマクロは次のようなイディオムを簡素化します。

(let1 var expr
  (if var then else))
Macro: rlet1 var expr body …

このマクロは次のようなイディオムを簡素化します。

(let1 var expr
  body ...
  var)
Macro: and-let* (binding …) body …

[SRFI-2] 簡単に言うと、このフォームはlet*のように動作しますが、 bindings中の式が#fに評価されたらそこで評価を打ち切り #fを返します。

bindingは以下のいずれかの形式でなければなりません。

(variable expression)

expressionが評価されます。それが真の値を返したら、その値がvariable に束縛され、次のbindingへと進みます。もうbindingが無ければ body …が評価されます。もしexpression#fを返したら、 評価を打ち切り、and-let*から#fを返します。

(expressionx)

この形式ではvariableが省略されています。Expressionが評価され、 その結果は評価を続行するか打ち切るかを判断するためにのみ使われます。

bound-variable

この形式ではbound-variableは束縛変数を示す識別子でなければなりません。 その変数の値が偽でなければ評価を続行します。

いくつか例を挙げます。次のコードは連想リストalistからkeyを 探し、見つかったらその値を返します。

(and-let* ((entry (assoc key alist))) (cdr entry))

もしargが正確な整数の文字列表現だった場合はnumを返し、そうでなければ 0を返します:

(or (and-let* ((num (string->number arg))
               ( (exact? num) )
               ( (integer? num) ))
      num)
    0)

以下のコードはとあるサーバーのポート番号をいくつかの可能性 (環境変数、設定ファイル…)の中から探す仮想的なコードです。

(or (and-let* ((val (sys-getenv "SERVER_PORT")))
      (string->number val))
    (and-let* ((portfile (expand-path "~/.server_port"))
               ( (file-exists? portfile) )
               (val (call-with-input-string portfile port->string)))
      (string->number val))
    8080) ; default
Macro: and-let1 var test exp1 exp2 …

testを評価し、それが#fでなければvarをその値に束縛して exp1 exp2 …を評価します。戻り値は最後の式の値です。 test#fだった場合は単に#fを返します。

これはand-let*if-let1を使って次のとおり書くこともできます。 しかし、このパターンを書くことがあまりに多いため、専用のマクロを用意する価値が あると判断しました。

(and-let1 var test
  exp1
  exp2 ...)

≡

(and-let* ([var test])
  exp1
  exp2 ...)

≡

(if-let1 var test
  (begin exp1 exp2 ...)
  #f)
Macro: fluid-let ((var val) …) body …

動的スコープの変数をエミュレートするマクロです。 varfluid-letフォームを含むスコープで定義されている 変数でなければなりません。valは式です。 fluid-letはまずvalを評価し、 valvarに動的スコープで束縛してbody … を評価します。

マルチスレッド環境下では、varの値の変化は全てのスレッドから見えます。 このフォームは主として他の処理系のコードを移植する際の利便性のために 追加されました。スレッドローカルな動的状態を実現するには、 パラメータオブジェクト(パラメータ参照)を 使って下さい。

(define x 0)

(define (print-x) (print x))

(fluid-let ((x 1))
  (print-x))  ⇒ ;; prints 1
Special Form: receive formals expression body …

[SRFI-8] この構文により、多値を受け取ることができます。 formalsはシンボルのリストです。不完全なリストであっても構いません。 expressionが評価され、返された値がlambda形式の引数の束縛と 同じようにしてformals内の変数と束縛され、その環境下でbody …が 評価されます。

(define (divrem n m)
  (values (quotient n m) (remainder n m)))

(receive (q r) (divrem 13 4) (list q r))
  ⇒ (3 1)

(receive all (divrem 13 4) all)
  ⇒ (3 1)

(receive (q . rest) (divrem 13 4) (list q rest))
  ⇒ (3 (1))

なお、多値call-with-valuesreceiveと等価な手続き的インタフェースです。 多値を複数の変数に同時に束縛するには、define-values (定義参照) が使えます。 また、下のlet-valueslet*-valuesletのような形式で多値を扱うことができます。

Macro: let-values ((vars expr) …) body …

[R7RS base] vars は変数のリストです。expr は評価され、最初の返り値は、 vars の最初の変数に束縛されます。二番目の返り値は、vars の 二番目の変数に束縛され、以下同様です。そのあと、body が評価されます。 expr のスコープは let と同様に、let-values の 外側になります。

(let-values (((a b) (values 1 2))
             ((c d) (values 3 4)))
  (list a b c d)) ⇒ (1 2 3 4)

(let ((a 1) (b 2) (c 3) (d 4))
  (let-values (((a b) (values c d))
               ((c d) (values a b)))
    (list a b c d))) ⇒ (3 4 1 2)

vars lambda パラメータと同様に、ドット付リストでも、 単一のシンボルでもかまいません。

(let-values (((x . y) (values 1 2 3 4)))
  y) ⇒ (2 3 4)

(let-values ((x (values 1 2 3 4)))
  x) ⇒ (1 2 3 4)

expr によって返された値の数と vars が期待する数とが一致しない 場合にはエラーになります。

Macro: let*-values ((vars expr) …) body …

[R7RS base] let-valuesと同じですが、各 expr のスコープが先行する vars を含みます。

(let ((a 1) (b 2) (c 3) (d 4))
  (let*-values (((a b) (values c d))
                ((c d) (values a b)))
    (list a b c d))) ⇒ (3 4 3 4)
Macro: rec var expr
Macro: rec (name . vars) expr …

[SRFI-31] 再帰的な参照のある式の評価を行うマクロです。

最初の形式は、varexprの結果に束縛される状態でexprを 評価します。 2番目の形式は以下の形式と等価です。

(rec name (lambda vars expr ...))

例:

;; constant infinite stream
(rec s (cons 1 (delay s)))

;; factorial function
(rec (f n)
  (if (zero? n)
      1
      (* n (f (- n 1)))))


For Development HEAD DRAFTSearch (procedure/syntax/module):
DRAFT