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

6.15 手続きと継続

Schemeでは、プログラムを組み立てる最も基本となるブロックが手続きです (手続きを作るも参照)。 手続きは、特定の計算を表現します。引数を取ることも出来ます。 そして、実引数に適用されると、その計算を実行します。 Schemeはまた、現在の計算の継続を取り出して手続きにラップして返す手段を 提供しています(継続参照)。

Gaucheは手続きの適用の概念を拡張し、どんなオブジェクトでもそれが手続きであるかのように 適用できるようにしました。例えば("abc" 2)が有効な適用であるように Gaucheをセットアップすることができます。 詳しくは適用可能なオブジェクトを参照してください。


6.15.1 procedureクラスと適用可能性

Builtin Class: <procedure>

手続きのクラスです。lambdaで作られた手続きや 組み込みのプリミティブ手続きはこのクラスのインスタンスです。 Gaucheではどんな型のオブジェクトも適用可能にできるので、あるオブジェクトが <procedure>クラスのインスタンスであるかとうかということは あまり気にする必要はありません。Gaucheの中身をいじってみる時以外は。

Function: procedure? obj

[R7RS base] obj生得的に適用可能なオブジェクトであれば#tを、 そうでなければ#fを返します。生得的にというのは、 Gaucheが最初から手続きとして呼び出せるオブジェクトとして備えているもの、程度の意味です。 <procedure>クラスのインスタンスや、 ジェネリックファンクションやメソッドがそうです。 (ジェネリックファンクションとメソッドに関しては ジェネリックファンクションとメソッドを参照してください)。

Gaucheでは、どんなオブジェクトも適用可能にすることができます (適用可能なオブジェクト参照)。従って、procedure?#fを 返したからといってそのオブジェクトを手続きのように呼び出せないとは限りません。 オブジェクトが適用可能かどうかをより正確に調べるには、 下に説明するapplicable?を使ってください。

Function: apply proc arg1 … args

[R7RS base] (arg1 … . args)を引数として手続きprocを呼びます。 最後の引数argsは正規のリストでなければなりません。 procが返す 値をそのまま返します。

(apply list 'a 'b '(c d e)) ⇒ (a b c d e)

(apply + 1 2 '(3 4 5))      ⇒ 15
Function: applicable? obj class …

それぞれの型がclass …であるような引数リストを伴って objを呼び出すことができるかどうかを調べます。 例えば(applicable? foo <string> <integer>)#tを 返したなら、foo(foo "x" -2)のように呼び出せるということです。 (これは、エラーが出ないことを保証するものではありません。fooは もしかする非負整数しか受け取れないかもしれませんが、そのことは applicable?の結果からはわかりません。でも、applicable?#tを 返したなら、fooを呼び出した時に“foo is not applicable”と Gaucheに文句を言われることはありません。)

この手続きは適用可能オブジェクトも考慮に入れます。 従って、例えば(applicable? #/a/ <string>)#tを返します。 正規表現は文字列に適用可能だからです (正規表現参照)。

ジェネリックファンクションに対しては、 渡されたclass引数それぞれが、対応する特定化子と一致するかサブクラスになっている ようなメソッドが最低ひとつあれば、applicable?#tを返します。

(define-method foo ((x <sequence>) (y <integer>)) #f)

(applicable? foo <sequence> <integer>) ⇒ #t
(applicable? foo <string> <integer>) ⇒ #t
(applicable? foo <hash-table> <integer>) ⇒ #f
(applicable? foo <string> <real>) ⇒ #f

2番目の例では、<string><sequence>のサブクラスなので #tが返ります。一方、3番目の例では<hash-table><sequence>のサブクラスではないので#fとなります。 4番めの例が#fなのは、<real><integer>のサブクラスではないからです。

伝統的なSchemeの手続き (lambdaで作られるようなもの) は 引数の数のみで呼び出し可能かどうかが判断されます。オブジェクトが 引数の型にかかわらず、特定の個数の引数を取るかどうかを判定するには、 class引数に<top>を渡します。(<top>は 全てのクラスのスーパークラスです。)

(applicable? cons <top> <top>) ⇒ #t

逆に、何らかの特定の型の引数を取るかどうかを調べたい場合は、 <bottom>を渡してください。(<bottom>は全てのクラスのサブクラスです。)

(define-method foo ((x <sequence>) (y <integer>)) #f)

(applicable? foo <top> <top>) ⇒ #f
(applicable? foo <bottom> <bottom>) ⇒ #t

<top>, <bottom>クラスおよびGaucheでの型の扱いについては 型とクラスを参照してください。

Function: procedure-type proc

引数は手続きでなければなりません。手続きprocの記述的な型を、 わかる範囲で返します。 手続きの型について詳しくは型とクラスを参照してください。

この手続きで返される型は一種のヒントにすぎません。 今のところ、ほとんどのSchemeで定義された手続きからは、あまり詳しい型情報は取れません。

(procedure-type filter) ⇒ #<^ <top> <top> -> *>

subr (Cで定義された手続き) の中には、Cの型情報を使って多少詳しい情報が 取れるものもあります。

(procedure-type cons) ⇒ #<^ <top> <top> -> <pair>>

いずれ、より正確な型情報を取れるようにして、 最適化やその他の静的解析に使えるようにする予定です。


6.15.2 万能アクセサ

Function: ~ obj key keys …
Function: (setter ~) obj key keys …

手続き~は、様々な集合型のオブジェクトの部分にアクセスするのに使えます。

;; Access to an element of a sequence by index
(~ '(a b c) 0)       ⇒ a
(~ '#(a b c) 2)      ⇒ c
(~ "abc" 1)          ⇒ #\b
(~ '#u8(10 20 30) 1) ⇒ 20

;; Access to an element of a collection by key
(~ (hash-table 'eq? '(a . 1) '(b . 2)) 'a)
  ⇒ 1

;; Access to a slot of an object by slot name
(~ (sys-localtime (sys-time)) 'hour)
  ⇒ 20

アクセスはチェインすることができます。

(~ '#((a b c) (d e f) (g h i)) 1 2) ⇒ f

(~ (hash-table 'eq? '(a . "abc") '(d . "def")) 'a 2)
  ⇒ #\c

~は左結合します。つまり、

(~ x k j) ≡ (~ (~ x k) j)

等。

一般化されたset!~に使えば、アクセスされる要素を置き換えることができます。

(define z (vector 'a 'b 'c))
(set! (~ z 1) 'Z)

z ⇒ #(a Z c)

(define z (vector (list (vector 'a 'b 'c)
                        (vector 'd 'e 'f)
                        (vector 'g 'h 'i))
                  (list (vector 'a 'b 'c)
                        (vector 'd 'e 'f)
                        (vector 'g 'h 'i))))

z ⇒ #((#(a b c) #(d e f) #(g h i))
     (#(a b c) #(d e f) #(g h i)))

(set! (~ z 1 2 0) 'Z)
z ⇒  #((#(a b c) #(d e f) #(g h i))
     (#(a b c) #(d e f) #(Z h i)))

~は内部的にジェネリックファンクションrefを使って実現されています。 ジェネリックファンクションについて詳しくはオブジェクトシステムを参照してください。

Generic function: ref object key :optional args …
Generic function: (setter ref) object key value

多くの集合型はこのジェネリックファンクションを特殊化し、 統一されたアクセス方法と変更方法を提供しています。 refのオプショナル引数argsの意味はメソッド毎に異なりますが、 最初のオプショナル引数は、objectkeyに対する値が無い場合の フォールバック値として使われるのが普通です。

正確な動作の定義は、refメソッドを提供しているクラスごとに説明されています。

~の動作は次のコードで理解できるでしょう。

(define ~
  (getter-with-setter
   (case-lambda
     [(obj selector) (ref obj selector)]
     [(obj selector . more) (apply ~ (ref obj selector) more)])
   (case-lambda
     [(obj selector val) ((setter ref) obj selector val)]
     [(obj selector selector2 . rest)
      (apply (setter ~) (ref obj selector) selector2 rest)])))

(Gaucheは最適化のためにいくつかの型で短絡経路を使うこともあるので、 実際の実装とは異なります)


6.15.3 コンビネータ

Gaucheには、combinatory programmingに使えるいくつかの基本手続きがあります。

Function: pa$ proc arg …

部分適用。手続きを返します。その手続きが引数m …を伴って 呼ばれた場合、それは(proc arg … m …)と等価になります。

(define add3 (pa$ + 3))
(add3 4) ⇒ 7

(map (pa$ * 2) '(1 2 3)) ⇒ (2 4 6)

SRFI-26で定義されているマクロcutcuteも似たような抽象化の 方法を提供しますが、pa$より多少柔軟性が高く、その分やや冗長です。 手続きを作るを参照して下さい。

SRFI-235で定義されるleft-sectionは実質的にpa$と同じです。 srfi.235 - コンビネータ (SRFI)参照。

Function: apply$ proc
Function: map$ proc
Function: for-each$ proc

apply, mapfor-eachの部分適用版です。

(define map2* (map$ (pa$ * 2)))
(map2* '(1 2 3)) ⇒ (2 4 6)
Function: count$ pred
Function: fold$ kons :optional knil
Function: fold-right$ kons :optional knil
Function: reduce$ f :optional ridentity
Function: reduce-right$ f :optional ridentity
Function: filter$ pred
Function: remove$ pred
Function: partition$ pred
Function: member$ item
Function: find$ pred
Function: find-tail$ pred
Function: any$ pred
Function: every$ pred
Function: delete$ pred
Function: assoc$ item

SRFI-1 (R7RS (scheme list)) (scheme.list - R7RSリスト参照)の手続に対応する部分適用版手続。

Function: .$ f …
Function: compose f …

複数の手続きを結合します。引数は全て手続きでなければなりません。 2つの引数が渡された時、(.$ f g)は次の式と等価です。

(lambda args (call-with-values (lambda () (apply g args)) f))

2つ以上の引数が渡された場合は、次のように結合されます。

(.$ f g h ...) ≡ (.$ (.$ f g) h ...)

いくつか例を示します。

(define not-zero? (.$ not zero?))
(not-zero? 3) ⇒ #t
(not-zero? 0) ⇒ #f

(define dot-product (.$ (apply$ +) (map$ *)))
(dot-product '(1 2 3) '(4 5 6)) ⇒ 32

境界のケース:ひとつだけ引数が渡された場合は、その引数がそのまま返されます。 引数が全く渡されなかった場合は手続きvaluesが返されます。

註: .$という名前は、文献やいくつかの他のプログラミング言語で .が関数合成によく使われること、そしてGaucheではコンビネータの末尾に $をつける習慣があることから来ています。ただ、これはR7RSの 範囲内では有効な識別子でないので、ポータビリティを考えるプログラムは 別名のcomposeを使った方が良いでしょう。そうすればSRFI-0などを 使って容易に移植が可能です。

なお、SRFI-210にcompose-leftcompose-rightが定義されています。 srfi.210 - 多値のための手続きと構文参照。

Function: identity obj

objを返します。ポータブルなコードではvaluesを使って同じ効果を 得ることができますが、これを使うことにより単一の値を扱っていることを強調できます。

註: SRFI-210は複数の値を扱うidentityを(残念ながら)導入してしまいました。 それはvaluesの別名にすぎません。 srfi.210 - 多値のための手続きと構文参照。

Function: constantly obj …

[SRFI-235] 任意個の引数をとり、それらを無視してobjを返す関数を返します。 (^ _ (values obj …))と同じです。

Function: complement pred

[SRFI-235] 述語predの意味を逆にした手続きを返します。すなわち、predが真を 返すような引数にたいして偽を返す、またその逆も同様であるような手続きです。

(map (complement even?) '(1 2 3)) ⇒ '(#t #f #t)
(map (complement =) '(1 2 3) '(1 1 3)) ⇒ '(#f #t #f)
((complement (lambda () #f))) ⇒ #t
Function: flip proc

[SRFI-235] 渡された引数を逆順でprocに渡すような手続きを返します。すなわち、 ((flip proc) a b … y z)(proc z y … b a)と同じです。

Function: swap proc

[SRFI-235] procは2つ以上の引数を取る手続きでなければなりません。 渡された引数の最初と2番めの引数の順序を入れ替えてprocを呼び出す手続きを 返します。すなわち、 ((swap proc) a b arg …)(proc b a arg …)と同じです。

Function: any-pred pred …

与えられた引数をそれぞれ述語predに適用する手続きを返します。 いずれかのpred#fでない値を返す場合、その値を返します。 全てのpred#fを返す場合、#fを返します。

(define string-or-symbol? (any-pred string? symbol?))
(string-or-symbol? "abc") ⇒ #t
(string-or-symbol? 'abc)  ⇒ #t
(string-or-symbol? 3)     ⇒ #f

(define <> (any-pred < >))
(<> 3 4) ⇒ #t
(<> 3 3) ⇒ #f

((any-pred (cut memq <> '(a b c))
           (cut memq <> '(1 2 3)))
 'b)  ⇒ '(b c)
Function: every-pred pred …

与えられた引数をそれぞれ述語predに適用する手続きを返します。 全てのpred#fでない値を返す場合、戻り値は最後の predの戻り値になります。いずれかのpred#fを 返す場合、every-predはそれ以降のpredを呼び出さずに #fを返します。

((every-pred odd? positive?) 3)  ⇒ #t
((every-pred odd? positive?) 4)  ⇒ #f
((every-pred odd? positive?) -3) ⇒ #f

(define safe-length (every-pred list? length))
(safe-length '(a b c))  ⇒ 3
(safe-length "aaa")     ⇒ #f

6.15.4 省略可能引数のパージング

Gaucheは省略可能引数やキーワード引数を拡張lambda構文で サポートしています (手続きを作る参照)。 けれども、Gauche拡張に頼らずに、以下のマクロを使って独自にこれらの引数を パーズすることもできます。

(define (foo a b :optional (c #f) (d 'none))
  body ...)

;; は次の式とだいたい同じ:

(define (foo a b . args)
  (let-optionals* args ((c #f) (d 'none))
    body ...))

明示的に拡張引数をパーズする方法は、ポータブルなコードを書く時に 役に立つでしょう。以下のマクロを実装するのは、lambdaの構文を 拡張するより簡単だからです。

また、共通する拡張引数の処理ルーチンを括り出す場合にもこれらのマクロは有用です。

Macro: let-optionals* restargs (var-spec …) body …
Macro: let-optionals* restargs (var-spec … . restvar) body …

与えられた値のリストrestargsを、var-specにしたがって 変数に束縛し、bodyを評価します。

var-specはシンボルか、そのcarがシンボルである2要素のリストの いずれかです。シンボルは束縛された変数名です。 restargsにある値は、順番にシンボルに束縛されます。 restargsvar-specに示される数の値がない場合は、 残りのsymbolは以下に従ってデフォルト値が束縛されます。 var-specが単なるシンボルなら、デフォルト値は未定義です。 var-specがリストなら、デフォルト値はリストの2番目の要素を 評価した結果です。後者の場合、2番目の要素は十分な引数がない場合にのみ 評価されます。 束縛はvar-specの順番にしたがって行われるので、2番目の要素は 以前のvar-specのバインディングを参照するかも知れません。

2番目のフォームでは、restvarはシンボルでなければならず、 var-specに束縛された後、restargsに残っている値のリストに 束縛されます。

restargvar-specよりも多い値を持っていてもエラーでは ありません。最初のフォームでは、余分な値は単に無視されます。

(define (proc x . args)
  (let-optionals* args ((a 'a)
                        (b 'b)
                        (c 'c))
    (list x a b c)))

(proc 0)         ⇒ (0 a b c)
(proc 0 1)       ⇒ (0 1 b c)
(proc 0 1 2)     ⇒ (0 1 2 c)
(proc 0 1 2 3)   ⇒ (0 1 2 3)

(define (proc2 . args)
  (let-optionals* args ((a 'a) . b)
    (list a b)))

(proc2)          ⇒ (a ())
(proc2 0)        ⇒ (0 ())
(proc2 0 1)      ⇒ (0 (1))
(proc2 0 1 2)    ⇒ (0 (1 2))

(define (proc3 . args)
  (let-optionals* args ((a 0)
                        (b (+ a 1))
                        (c (+ b 1)))
    (list a b c)))

(proc3)          ⇒ (0 1 2)
(proc3 8)        ⇒ (8 9 10)
(proc3 8 2)      ⇒ (8 2 3)
(proc3 8 2 -1)   ⇒ (8 2 -1)
Macro: get-optional restargs default

これはlet-optionals*の短いバージョンで、オプショナル引数が 1つしかないときに使います。オプショナル引数のリストとしてrestargsが 与えらると、このマクロはオプショナル引数が与えられていればその値を返し、 そうでなければdefaultの結果を返します。defaultrestargsが 空リストでなければ評価されません。

(define (proc x . maybe-opt)
  (let ((option (get-optional maybe-opt #f)))
    (list x option)))

(proc 0)         ⇒ (0 #f)
(proc 0 1)       ⇒ (0 1)
Macro: let-keywords restarg (var-spec …) body …
Macro: let-keywords restarg (var-spec … . restvar) body …

このマクロはキーワード引数のためのものです。var-specは 以下のフォームのうちのいずれかです。

(symbol expr)

restargsymbolと同じ名前を持つキーワードを含んでいる場合、 symbolを対応する値に束縛します。そのようなキーワードがrestargに ない場合は、symbolexprの結果に束縛します。

(symbol keyword expr)

restargがキーワードkeywordを含む場合、 symbolを対応する値に束縛します。そのようなキーワードがrestargに ない場合、symbolexprの結果に束縛します。

デフォルト値exprは、restargにキーワードが与えられてなかった 場合にのみ評価されます。

1番目のフォームでは、var-specにないキーワード引数がrestargに 現れるとエラーとなります。 他のキーワード引数を許したい場合は次の2番目のフォームを使ってください。

2番目のフォームでは、restvarはシンボルか#fでなければなりません。 シンボルのときは、var-specに束縛されなかったrestargsのキーワード リストがrestvarに束縛されます。#fのときは、それらのrestargs のキーワードは単に無視されます。

(define (proc x . options)
  (let-keywords options ((a 'a)
                         (b :beta 'b)
                         (c 'c)
                         . rest)
    (list x a b c rest)))

(proc 0)         ⇒ (0 a b c ())
(proc 0 :a 1)    ⇒ (0 1 b c ())
(proc 0 :beta 1) ⇒ (0 a 1 c ())
(proc 0 :beta 1 :c 3 :unknown 4) ⇒ (0 a 1 3 (:unknown 4))
Macro: let-keywords* restarg (var-spec …) body …
Macro: let-keywords* restarg (var-spec … . restvar) body …

このマクロはlet-keywordsとほぼ同じですが、束縛がvar-specでの 順番に行われるところが異なります。exprは以前のvar-specにより 束縛された変数を参照できます。


6.15.5 手続きのアリティ

手続きのアリティを問い合わせるインターフェースです。 APIは、MzScheme (PLT Scheme)を参考にしました。

Function: arity proc

手続きprocを与え、整数、arity-at-leastオブジェクト、 整数とarity-at-leastオブジェクトからなるリストのいずれかを 返します。

整数の戻り値は、procが正確にその数の引数を取ることを表します。 arity-at-leastは、procが最低でも 引数(arity-at-least-value arity-at-least)を取ることを 表します。リストは、異なるアリティを持つ複数の手続きがあることを 表します。

Gaucheではいつでも、既存の手続きやジェネリック関数にメソッドを追加 できるので、arityが返す値はその手続きの現在の状態を示すに 過ぎません。その手続きやジェネリック関数に新しいメソッドが追加 されると、それも変更されます。

(arity cons) ⇒ 2
(arity list) ⇒ #<arity-at-least 0>
(arity make) ⇒ (#<arity-at-least 1>)
Function: arity-at-least? obj

objがarity-at-leastオブジェクトなら、真を返します。

Function: arity-at-least-value arity-at-least

arity-at-leastオブジェクトが表す必須引数の数を返します。

Function: procedure-arity-includes? proc k

手続きprocが引数kを取れる場合、#tを返します。 そうでなければ#fを返します。


6.15.6 適用可能なオブジェクト

Gaucheでは、特別な組み込みの機構によって任意のオブジェクトを 「適用可能」にすることができます。

Generic Function: object-apply object arg

手続きでもジェネリックファンクションでもないオブジェクトが何らかの引数に 適用されたとき、そのオブジェクトと引数がジェネリックファンクションobject-apply に渡されます。

この機能は、具体的な例を挙げた方が説明し易いでしょう。

例えば、次のような式を評価しようとしたとします。

("abcde" 2)

オペレータは文字列に評価されますから、手続きでもジェネリックファンクションでも ありません。そこで、Gaucheはこの式を、あたかも次のような式が与えられた かのように解釈します。

(object-apply "abcde" 2)

デフォルトでは、<string><integer>を引数とする object-applyのメソッドは定義されていないので、 この式はエラーになります。しかし、次のようなメソッドを定義すると:

(define-method object-apply ((s <string>) (i <integer>))
  (string-ref s i))

最初の式はまるで文字列が整数に適用されたかのように動作します。

("abcde" 2) ⇒ #\c

このメカニズムは手続きが許されるほとんどの箇所で使うことができます。

(apply "abcde" '(1))   ⇒ (#\b)
(map "abcde" '(3 2 1)) ⇒ (#\d #\c #\b)

Gauche組み込みオブジェクトのうち、<regexp>オブジェクトと <regmatch>オブジェクトに対してはobject-applyメソッドが定義されて います。正規表現を参照して下さい。

註: object-applyメソッドの定義はグローバルに影響します。 上の例のように文字列にobject-applyを定義するのはおもしろいですが、 一般的なトリックとしては推奨されません。これによってプログラム全体での 文字列の振る舞いが変わってしまうからです。

上の例は個人的な実験に留めておきましょう。一般的には、 自分で定義したクラスに対してのみobject-applyを定義するのが良いです。

Generic Function: (setter object-apply) object argvalue

適用可能オブジェクトを適用するフォームがset!フォームの第一ポジションに 現れた場合、そのフォームは下に示すように展開され、このメソッドが呼ばれます。

(set! (object arg ...) value)
 ⇒ ((setter object-apply) object arg ... value)

6.15.7 継続

Function: call-with-current-continuation proc
Function: call/cc proc

[R7RS base] 現在の継続を手続き (継続手続き) にパッケージ化して、それを引数として procを呼び出します。procが戻ったら、その返り値がcall/ccの 値となります。作成された継続手続きがどこかで0個または複数個の引数を伴って呼ばれたら、 あたかもcall/ccから戻ったかのように実行が継続されます。その場合、 call/ccは、継続手続きに与えられた引数を複数の値として返します。

ファーストクラスの継続はSchemeの最も特徴的な機能のひとつですが、それを 十分に説明するにはこの本の余白は狭すぎます。適切なドキュメントを参照してください。

Schemeの継続とC言語の実行環境との間に、ちょっとわかりずらい干渉が生じることがあります。 次のシナリオを考えます。

  1. アプリケーションのCランタイムがSchemeで書かれた手続きをコールバックします。 例えば、GUIフレームワークがSchemeで書かれた描画ルーチンを呼ぶ、というようなケースを考えてください。
  2. そのSchemeルーチンで継続が捕捉される。
  3. Schemeルーチンが終了してCランタイムに制御を戻す。
  4. 2で捕捉した継続が起動される。

継続を起動すること自体には問題は無いのですが、 制御がSchemeからCへと再び戻ろうとすると (つまり、step 3が再び実行されようとすると) 次のようなエラーが投げられます。

*** ERROR: attempt to return from a ghost continuation.

これは、Cの世界では関数が1回より多く戻ってくることを想定していないからです。 最初にSchemeのコールバックが呼ばれた時のCのスタックフレームは、 継続が再び呼ばれた時には状態が変わっているか、捨てられてしまっているでしょう。

継続を、根から上へ向かって成長する制御フレームの連鎖のようにイメージした場合、 Cの世界へ戻った時点でその連鎖が断ち切られる、と考えることができます。 そのような根無しの継続も実行することはできますが、既に失った根に戻る前に 別の箇所へと制御を移さねばなりません。他の箇所で捕まえた継続を呼んだり、 例外を投げるといったことが考えられます。

部分継続(限定継続)を使うのも手です。 gauche.partcont - 部分継続を参照してください。

Macro: let/cc var body …

このマクロは次のように展開されます : (call/cc (lambda (var) body …)). APIはPLT Schemeから取りました。

Function: dynamic-wind before body after

[R7RS base] これは動的環境を管理するための基本手続きです。 動的環境とは、ある式を実行している間、維持される状態のセットのことです。 例えば「現在の出力ポート」というのは、with-output-to-port手続きの 実行中だけ切り替えることができます。 動的環境は動的にネストできます。これは、プログラムのソースから静的に ネストが決定できる字句環境とは異なります。

beforebodyおよびafter は引数を取らない手続きです。 dynamic-windはまずbeforeを呼び出し、続いてbodyを呼び出し、 続いてafterを呼び出します。そしてbodyが返した値を返します。

before手続きがbodyを実行するための動的環境を 設定し、after手続きが動的環境を元に戻す、ということが意図されています。

もしdynamic-windのダイナミックスコープの外で捕捉された継続が bodyの中で呼ばれることにより制御がbodyから飛び出した場合、 (bodyの中でエラーが起こった場合などが考えられます)、 afterが呼ばれます。

もし、bodyの中で捕捉された継続がdynamic-windのダイナミックスコープの 外で呼ばれることにより制御がbodyの中へ飛び込んだ場合、 beforeが呼ばれます。

(letrec ((paths '())
         (c #f)
         (add (lambda (s) (push! paths s))))
  (dynamic-wind
   (lambda () (add 'connect))
   (lambda ()
     (add (call/cc (lambda (c0) (set! c c0) 'talk1))))
   (lambda () (add 'disconnect)))
  (if (< (length paths) 4)
      (c 'talk2)
      (reverse paths)))
 ⇒ (connect talk1 disconnect connect talk2 disconnect)

註:エラーによりbodyが中断された時に必ずafterが呼ばれることから、 dynamic-windを例えばJavaのtry-catch構文のように考えて リソースの後処理などに使いたくなるかもしれません。 しかしdynamic-windはそのためのものではありません。 一旦離れた制御がbodyに再び戻ってくる可能性があるので、 dynamic-windが管理するのはむしろコンテキストスイッチに近い状況です。

リソースの後処理には、guardunwind-protectなどの例外処理 が使えます(上位レベルの例外処理機構参照)。 それらはdynamic-windを使って構築されています。

基本的な指針として、afterは常にbeforeによって効果を戻せる 処理だけを行うべきです。例えば、(エラーを直接処理するのではなく) エラーハンドラスタックを操作する、といった具合に。


6.15.8 継続プロンプト

継続はよく「計算の残りの部分」と説明されます。理論家が論文を書くにはそれで充分なんですが、 現実には、「未来永劫の計算全て」を具体化することはできません。 どこかに終わりがないと困ります。それはひとつのプログラムの終了かもしれませんし、 サーバーがリクエストに対するレスポンスを送り終えるまでかもしれませんし、 REPLが与えられた式の評価結果を出力し終わるまでかもしれませんが。 従来は、実装が必要に応じて継続の「終わり」を選んできたのですが、 これはしばしば混乱の元になっていました。

継続プロンプトは、継続の「終わり」を明示するものです。 「プロンプト」の名前の由来は、インタラクティブな環境ではREPLのプロンプトが 継続の区切りになっていることです。アクティベーションレコードを良くあるスタックモデルで 理解するなら、継続プロンプトはスタックの「底」にあたります。

さらに、複数種類の継続プロンプトを使うことができます。これは、 継続プロンプトを利用して実装された複数の独立した機能が、うっかり干渉し合わないように するためのものです。

(部分継続は継続プロンプトを使って実装でき、実際、SRFI-226はそのように定義しています。 Gaucheの従来の部分継続は将来のバージョンで継続プロンプトと統合される予定です。)

Function: make-continuation-prompt-tag :optional name

[SRFI-226] 新たな継続プロンプトタグを作って返します。 継続プロンプトタグは、継続フレームの境界を指定するのに使うオブジェクトです。 新しい境界はcall-with-continuation-promptで導入されます。 ひとつの継続プロンプトタグを使いまわして、継続フレームの境界をネストすることもできます。 継続プロンプトタグを取る手続きは、そのタグを持つ最も内側の境界を使います。

省略可能なnameは継続プロンプトタグの名前です。 これは単に、診断を容易にするために表示するためだけのものです。

Function: default-continuation-prompt-tag

[SRFI-226] デフォルト継続プロンプトタグを返します。 プログラムが開始された時、最初の継続フレームの境界はデフォルト継続プロンプトタグを持ちます。 継続プロンプトタグを省略可能引数として取る手続きはほとんど、 省略時にデフォルト継続プロンプトタグを使います。

Function: continuation-prompt-tag? obj

[SRFI-226] objが継続プロンプトタグなら#tを、そうでなければ#fを返します。

Function: call-with-continuation-prompt thunk :optional prompt-tag abort-handler

[SRFI-226] 継続プロンプトタグprompt-tagで区切られた継続の上でthunkを呼び出します。 prompt-tagが省略された場合はデフォルト継続プロンプトタグが使われます。

abort-handlerは手続きで、thunk実行中にその継続が abort-current-continuationによって破棄された場合に呼ばれます。 abort-current-continuationに渡されるオブジェクトがabort-handler に引数として渡されます。 abort-handler自身は、call-with-continuation-promptの継続の上で 実行されます。 thunkが実行中に継続を破棄しなかった場合はabort-handlerは呼ばれません。

abort-handlerが省略された場合はデフォルトのアボートハンドラが使われますが、 それはちょっと特殊な動作をします。まず、デフォルトアボートハンドラは 一つだけ引数を取り、それはサンクでなければなりません (ここではcontinuation-thunkと呼ぶことにします)。 デフォルトアボートハンドラはprompt-tagで区切られた継続を改めて導入し、 continuation-thunkを呼び出します。いわば、トランポリンのように、 thunkがもともと呼ばれた文脈に制御を戻すようなものです。

例については下のabort-current-continuationの項を参照してください。

Function: abort-current-continuation prompt-tag obj …

[SRFI-226] 現在の継続から、prompt-tagでタグ付けられた継続プロンプトまでの 継続を破棄して、そのprompt-tagを導入した call-with-continuation-promptのアボートハンドラに制御を移します。 残りの引数obj …はアボートハンドラに引数として渡されます。

現在の継続の先にprompt-tagを持つ継続プロンプトが無かった場合は <continuation-violation>エラーが投げられます。

(let ([tag (make-continuation-prompt-tag)])
 (call-with-continuation-prompt
   (^[] (+ 1 (abort-current-continuation tag 'foo 'bar) 2))
   tag
   list))
 ⇒ (foo bar)

もし動的ハンドラが破棄される継続フレーム中に挿入されていたら、 その ‘after’ ハンドラはアボートハンドラの前に実行されます。

(let ((tag (make-continuation-prompt-tag)))
 (call-with-continuation-prompt
   (^[]
     (dynamic-wind
       (^[] #f)
       (^[] (abort-current-continuation tag 'foo))
       (^[] (print 'yo))))
   tag
   print))
 ⇒ prints yo, then prints foo.

ここまで読んできて、従来のcall/ccに対して、 call-with-continuation-promptabort-current-continuation の組み合わせがどう違うのかと思ったかもしれません。 実は後者はより低レベルの構造で、call/ccはその上に実装される高レベル構造なのです。


6.15.9 継続マーク

継続マークは、継続フレームに付加されるキーと値の関連です。 キーと値には任意のSchemeuオブジェクトが使えます。 これだけ聞いてもなんだかよく分からないかもしれませんが、 継続マークを使って色々便利な言語機能を実装することができます。 例えば、新しいparameterizeは本体を末尾コンテクストで評価することが できますが、それは継続マークを使って実装されているからです。

ただ、一般的には継続マークは他の高レベルな言語機能を 実装するための低レベルなツールと考えるべきでしょう。 日常的なコードで使うものではありません。

継続フレームをマークする

Special Form: with-continuation-mark key value expr

[SRFI-226] まずkeyvalueが評価され、そのキー-値の関連が 現在の継続のマークに追加されます。 そして、exprがこのフォームの継続と同じ継続で評価されます。 つまり、このフォーム自体が末尾コンテクストにあれば、 exprも末尾コンテクストで評価されます。

現在の継続フレームのマークに既にkeyがあれば、その値がvalueで置き換えられます。

Macro: with-continuation-marks ((key value) …) expr

[SRFI-226] keyvalueがすべて評価された後、それらキー-値ペア全てが 現在の継続のマークに追加され、同じ継続でexprが評価されます。

継続マークを取り出す

Function: call-with-immediate-continuation-mark key proc :optional fallback

[SRFI-226] 現在の継続のマーク中のkeyに対応する値を引数として、procを末尾呼び出しします。 現在の継続のマーク中にkeyがなければfallbackが使われます。 fallbackが省略された場合は#fが使われます。

(with-continuation-mark 'a 'b
  (call-with-immediate-continuation-mark 'a list))
 ⇒ (b)

(with-continuation-mark 'a 'b
  (cons (call-with-immediate-continuation-mark 'a list) 1))
 ⇒ ((#f) . 1)
Function: continuation-marks cont :optional prompt-tag

[SRFI-226] 継続contからprompt-tagで区切られる継続フレームまでの継続マークを 集めた、継続マークセットオブジェクトを返します。 prompt-tagが省略された場合はデフォルトプロンプトタグが使われます。

Function: current-continuation-marks :optional prompt-tag

[SRFI-226] 現在の継続からprompt-tagで区切られる継続フレームまでの継続マークを 集めた、継続マークセットオブジェクトを返します。 prompt-tagが省略された場合はデフォルトプロンプトタグが使われます。

継続マークセット

Function: continuation-mark-set? obj

[SRFI-226] objが継続マークセットなら#tを、そうでなければ#fを返します。

Function: continuation-mark-set->list mark-set key :optional prompt-tag

[SRFI-226] 継続マークセットmark-setから、keyを持つマークに対応する値を 全て取り出し、新しいリストにして返します。より新しい継続フレームに対応する値ほど リストの先頭の方に置かれます。

mark-set#fを渡すこともできます。その場合、 (current-continuation-marks prompt-tag)が使われます。

keyは、継続マークセット中の、prompt-tagで指定される継続プロンプト に対応する継続マークまで探されます。prompt-tagが省略された場合は デフォルトプロンプトタグが使われます。

Function: continuation-mark-set-first mark-set key :optional fallback prompt-tag

[SRFI-226] 継続マークセットmark-setのうち、keyに結びつけられた最も新しい値を 返します。もしkeyがマークセットの中にあるなら、 値は(car (continuation-mark-set->list key prompt-tag)と同じになります。

keyがマークセット中に無い場合はfallbackが返ります。 fallbackが省略された場合は#fになります。

prompt-tagが省略された場合はデフォルトプロンプトタグが使われます。

継続マークキー

継続マークのキーには任意のSchemeオブジェクトを使えますが、 例えばライブラリを書いている場合、使っているキーが偶然他で使われているキーと かぶってしまうのは望ましくありません。

継続マークキーは、他の継続マークキーと(equal?で)等しくならないことが 保証されているオブジェクトです。

Function: make-continuation-mark-key :optional name

[SRFI-226] これ以前の継続マークキーとも、以降のマークキーとも決して重ならない 新たな継続マークキーを作って返します。

name引数はデバッグ用です。

SRFI-226は継続マークキーが他のSchemebオブジェクトと異なる型であることを 要請していないことに注意してください。今のところ、Gaucheではインターンされていない シンボルを継続マークキーとして返しています。これは将来変わるかもしれないので、 それに依存したコードは書かないようにしてください。

Function: continuation-mark-key? obj

[SRFI-226] objmake-continuation-mark-keyが返したオブジェクトである可能性が あれば#tを、そうでなければ#fを返します。

SRFI-226は継続マークキーが独自の型を持つことを要請していないので、この手続きは objmake-continuation-mark-keyで作られたものでなくても#tを 返し得ることに注意してください。 実際、Gaucheは今のところインターンされていないシンボルを継続マークキーとして 返しているので、この手続きはシンボルに対して#tを返します。


6.15.10 多値

Function: values obj …

[R7RS base] obj … を多値として返します。 呼び出し側は、組み込み構文の receivelet-values (変数束縛参照)か、 下に説明するR7RSの手続きcall-with-valuesを使って多値を受け取ることが できます。

(values 1 2) ⇒ 1 and 2
Function: call-with-values producer consumer

[R7RS base] 手続きproducerを引数無しで呼びます。そして、それが返した値 を引数としてconsumerを呼びます。consumerが返す値を 返します。

(call-with-values (lambda () (values 1 2)) cons)
  ⇒ (1 . 2)
Macro: values-ref mv-expr k

mv-exprが返す多値のk-番目の値を返します。概念としては、 以下のコードと同じです。

(call-with-values (lambda () mv-expr) (lambda r (list-ref r k)))

このマクロは k がゼロであるような典型的な場合にはより単純な形へと 展開されます。

Common Lisp の nth-value に似ていますが、引数の順が逆になっています。 Scheme の他の*-ref 手続きと合わせるためです。

SRFI-210のvalue/mvvalues-refの拡張になっています (srfi.210 - 多値のための手続きと構文参照)。

(values-ref mv-expr k) ≡ (value/mv k mv-expr)
Macro: values->list mv-expr

mv-exprを評価し、結果の値をリストにして返します。Common Lispで multiple-value-listと呼ばれているものです。

(values->list (div-and-mod 10 3)) ⇒ (3 1)

(values->list 1) ⇒ (1)

SRFI-210のlist/mvvalues->listの拡張になっています (srfi.210 - 多値のための手続きと構文参照)。

(values->list mv-expr) ≡ (list/mv mv-expr)

6.15.11 生成された値の畳み込み

一回呼ばれる度にひとつの値を生成するような手続きは、 一連の値を生成するジェネレータとして使われることがあります。 値の終端を表すマークとしては、慣例としてEOFが使われます。 例えばread-charはそうした、一連の文字を生成し、終端としてEOFを返す手続きです。

こうした抽象化は手軽であるため、Gaucheは、他のジェネレータを含むさまざまなソースから ジェネレータを構築するユーティリティをひとそろい提供しています。

生成された値は最終的に消費されなければなりません。そのための手続きもいくつか 提供されています。こうした手続きは、readのような入力手続きと組み合わせるのに 有用です。このため、別のモジュールに分割するのではなく、組み込み手続きになっています。

Function: generator-fold proc seed gen gen2 …

[R7RS generator] ジェネレータである手続き gen gen2 … が生成する値に対して fold のように働きます(foldの詳細はリストをたどる手続き参照)。

引数としてジェネレータがひとつ与えられると、その gen が生成する各値 v に 対して、proc(proc v r) のように呼び出されます。 rは現在の積算結果であり、その初期値は seed です。そして、proc が 返す値が次回の proc 呼び出し時の積算値として渡されることになります。 genがEOFを返すと、その時点の積算値が generator-fold から値として 返されます。

2つ以上のジェネレータが渡された場合は、proc(proc v1 v2r) のように呼び出されます。 v1, v2 … はそれぞれ gen, gen2, … が生成 した値であり、rは現在の積算値です。ジェネレータのどれかひとつ以上がEOFを返すと、 イテレーションは終了します。

(with-input-from-string "a b c d e"
  (cut generator-fold cons 'z read))
  ⇒ (e d c b a . z)
Function: generator-fold-right proc seed gen gen2 …

ジェネレータである手続きgen gen2 …が生成する値に対して fold-rightのように働きます(fold-rightの詳細はリストをたどる手続き参照)。

この手続きは完全性のために提供されていますが、ジェネレータとの相性はあまり良くありません。 値を右結合で計算していくためには、ジェネレータから(少なくともどれかひとつのジェネレータがEOFを返すまで) 全ての値を読み取らなければなりません。そうして初めて proc の呼び出しが始まることになります。

(proc v0_0 v1_0 ... (proc v0_1 v1_1 ... (proc v0_n v1_n ... seed) ...))

vn_mは、n番目のジェネレータがm回目の呼び出しで返した値です。

(with-input-from-string "a b c d e"
  (cut generator-fold-right cons 'z read))
  ⇒ (a b c d e . z)

ごらんのように、全ての中間値を保持することで、ジェネレータの利点が ある意味帳消しになってしまうのです。

Function: generator-for-each proc gen gen2 …

[R7RS generator] ジェネレータ版のfor-eachです。gen, gen2 … が生成する 値に対して、ジェネレータのどれかがEOFを返すまで proc を繰り返し適用していきます。 procが返す値は無視されます。

これは、生成される値を副作用で消費するのに便利です。

Function: generator-map proc gen gen2 …

ジェネレータ版のmapです。gen, gen2 … が生成する 値に対して、ジェネレータのどれかがEOFを返すまで proc を繰り返し適用していきます。 procが返す値をひとつのリストに束ねて返します。

(with-input-from-string "a b c d e"
  (cut generator-map symbol->string read))
  ⇒ ("a" "b" "c" "d" "e")

generator->listgmap(ジェネレータの操作参照)の組み合わせと 同じ挙動を実現します。この手続きは後方互換性のために提供されています。

(generator->list (gmap proc gen gen2 ...))
Function: generator-find pred gen

[R7RS generator] ジェネレータgenから返された中から、述語predを満たす最初の要素を返します。

以下の例は、ファイルfoo.txtの中から正規表現#/XYZ/にマッチする最初の行を返します。

(with-input-from-file "foo.txt"
  (cut generator-find #/XYZ/ read-line))

註: grepコマンドのように、正規表現にマッチする全ての行を取り出したいなら、 gfiltergenerator->listが使えます。



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