R6RS:翻訳:R6RS:11.15 Control features

R6RS:翻訳:R6RS:11.15 Control features

11.15 制御機能

本章ではプログラムの実行の流れを特別な方法で制御する様々な基本手続きについて述べる。

[procedure] (apply proc arg1 ... rest-args)

rest-args はリストでなければならない。 procn 個の引き数を取る。ここで、 narg の個数に rest-args の長さを足したものである。 apply 手続きは (append (list arg1 ...) rest-args) を要素とするリストを実引き数として proc を呼び出す。

apply の呼び出しが末尾文脈に現れた場合、 proc への呼び出しも末尾文脈内になる。

(apply + (list 3 4))                      ⇒  7

(define compose
  (lambda (f g)
    (lambda args
      (f (apply g args)))))

((compose sqrt *) 12 75)                      ⇒  30

[procedure] (call-with-current-continuation proc)

[procedure] (call/cc proc)

proc は引き数をひとつ取らなければならない。 call-with-current-continuation 手続き(これは call/cc 手続きと同じである)は現在の継続を「脱出手続き」としてパッケージ化し、それを proc の引き数として渡す。脱出手続きは Scheme の手続きであり、後に呼び出されると、その時に有効な継続を何であれ放棄し、代わりに脱出手続きが作成されたときに有効な継続に復帰する。脱出手続きを呼び出すと dynamic-wind によって導入された beforeafter 手続きが実行されることがある。

脱出手続きは call-with-current-contintualtion のもとの呼び出しの継続と同じ数だけの引き数を取る。

proc に渡された脱出手続きは他の任意 Scheme の手続きとまったく同様に無制限の存続期間を持つ。変数やデータ構造に格納することもでき、望んだ回数だけ呼び出すこともできる。

call-with-current-continuation の呼び出しが末尾文脈に現れた場合、 proc の呼び出しも末尾文脈内にある。

次の例に call-with-current-continuation の使い方を少しだけ示す。実際に使われるのがこの場合とおなじくらい単純であったなら、 call-with-current-continuation のような力を持つ手続きは必要なかったであろう。

(call-with-current-continuation
  (lambda (exit)
    (for-each (lambda (x)
                (if (negative? x)
                    (exit x)))
              ’(54 0 37 -3 245 19))
    #t))                                ⇒  -3

(define list-length
  (lambda (obj)
    (call-with-current-continuation
      (lambda (return)
        (letrec ((r
                  (lambda (obj)
                    (cond ((null? obj) 0)
                          ((pair? obj)
                           (+ (r (cdr obj)) 1))
                          (else (return #f))))))
          (r obj))))))

(list-length ’(1 2 3 4))                    ⇒  4

(list-length ’(a b . c))                    ⇒  #f

(call-with-current-continuation procedure?)
                                    ⇒  #t

: 脱出手続きの呼び出しは call-with-current-continuation の呼び出しの動的存続期間に再入し、その動的環境を復帰させる。 R6RS:翻訳:R6RS:5.12 Dynamic extent and the dynamic environment 参照。

[procedure] (values obj ...)

その引き数すべてをその継続に引き渡す。 values 手続きは次のように定義することもできる。

(define (values . things)
  (call-with-current-continuation 
    (lambda (cont) (apply cont things))))

lambda、 begin、 let、 let*、 letrec、 letrec*、 let-values、 let*-values、 case、 cond フォーム内の式の並びのうち、最後の式以外の継続はすべて、普通、任意の個数の値を受け取る。

これらの継続と call-with-values、 let-values、 let*-values によって作られた継続以外はちょうどひとつの値を受け取り、手続き呼び出しの <operator> や <operand>、 条件分岐の <test> などの継続は、暗黙裏にただひとつの値を受け取る。そのような継続に不適切な個数の値を渡した場合の効果は規定されていない。

[procedure] (call-with-values producer consumer)

producer は 0 個の引き数を受け取る手続きであり、consumerproducer の返した値と同じ数の値を受け取る手続きでなければならない。 call-with-values 手続きは無引き数で producer を呼び出し、consumer に値が渡された場合、その値を引き数として consumer 手続きを呼び出す継続を呼び出す。 consumer の呼び出しへの継続は call-with-values への呼び出しの継続である。

(call-with-values (lambda () (values 4 5))
                  (lambda (a b) b))
                                                           ⇒  5

(call-with-values * -)                                     ⇒  -1

call-with-values の呼び出しが末尾文脈に現れた場合、 consumer への呼び出しも末尾文脈内にある。

実装系の義務: 手続きが戻った後に、実装系は consumerconsumer の返しただけの値を受け取ったか確認しなければならない。

[procedure] (dynamic-wind before thunk after)

beforethunkafter は手続きでなければならず、それぞれ 0 個の引き数を取る。これらの手続きは任意の個数の値を返してもよい。 dynamic-wind 手続きは thunk を引き数なしで呼び出し、この呼び出しの結果を返す。さらに、 dynamic-wind の呼び出しは thunk への呼び出しの動的存続期間突入するに場合に常に before を引き数なしで呼び出し、 thunk への呼び出し動的存続期間から脱出するときに常に after を引き数なしで呼び出す。したがって、 call-with-current-continuation によって作成された脱出手続きの呼び出しがない場合には、 dynamic-wind は beforethunkafter をこの順番で呼び出す。

beforeafter への呼び出しが thunk の動的存続期間内にはないと解釈される一方で、 thunk の呼び出しの動的存続期間内に現れる他の任意の dynamic-wind の呼び出しの beforeafter の呼び出しは、 thunk の動的存続期間内にあるものと解釈される。

より精確に言うと、脱出手続きは 0 個以上の活性な dynamic-wind の呼び出し x ... の集合の動的存続期間の外側に制御を移し、 0 個以上の活性な dynamic-wind の呼び出し y ... の集合の動的存続期間の内側に制御を移す。最も最近の x の動的存続期間はそのままに、対応する after 手続きを引き数なしで呼び出す。 after 手続きから返った場合、次に最も最近設定された x を開始し、というように続けていく。各 x が一度このように扱われると、脱出手続きは最も外側の y に対応する before 手続きを引き数なしで呼び出す。 before 手続きが返ると、脱出手続きはより内側の y の動的存続期間に再入し、次の y に進む、というようにしていく。各 y が一度このように扱われると、脱出手続きにパッケージ化された継続に制御が移る。

実装系の義務: 実装系は thunkafter が実際に呼び出される場合にだけ、その制約を確認しなければならない。

(let ((path ’())
      (c #f))
  (let ((add (lambda (s)
               (set! path (cons s path)))))
    (dynamic-wind
      (lambda () (add ’connect))
      (lambda ()
        (add (call-with-current-continuation
               (lambda (c0)
                 (set! c c0)
                 ’talk1))))
      (lambda () (add ’disconnect)))
    (if (< (length path) 4)
        (c ’talk2)
        (reverse path))))
    
                ⇒ (connect talk1 disconnect
               connect talk2 disconnect)

(let ((n 0))
  (call-with-current-continuation
    (lambda (k)
      (dynamic-wind
        (lambda ()
          (set! n (+ n 1))
          (k))
        (lambda ()
          (set! n (+ n 2)))
        (lambda ()
          (set! n (+ n 4))))))
  n)         ⇒ 1

(let ((n 0))
  (call-with-current-continuation
    (lambda (k)
      (dynamic-wind
        values
        (lambda ()
          (dynamic-wind
            values
            (lambda ()
              (set! n (+ n 1))
              (k))
            (lambda ()
              (set! n (+ n 2))
              (k))))
        (lambda ()
          (set! n (+ n 4))))))
  n)         ⇒ 7

: 動的存続期間に入るとその動的環境が復元される。R6RS:翻訳:R6RS:5.12 Dynamic extent and the dynamic environment 参照。


Last modified : 2008/03/31 14:40:21 UTC