For Gauche 0.9.5


Next: , Previous: , Up: 組み込みライブラリ   [Contents][Index]

6.18 手続きと継続

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

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


Next: , Previous: , Up: 手続きと継続   [Contents][Index]

6.18.1 Procedure class and applicability

Builtin Class: <procedure>

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

Function: procedure? obj

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

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

Function: apply proc arg1 … args

[R7RS] (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での型の扱いについては 型とクラスを参照してください。


Next: , Previous: , Up: 手続きと継続   [Contents][Index]

6.18.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は最適化のためにいくつかの型で短絡経路を使うこともあるので、 実際の実装とは異なります)


Next: , Previous: , Up: 手続きと継続   [Contents][Index]

6.18.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$より多少柔軟性が高く、その分やや冗長です。 手続きを作るを参照して下さい。

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(リストライブラリ参照)の手続に対応する部分適用版手続。

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などを 使って容易に移植が可能です。

Function: complement pred

述語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: 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

Next: , Previous: , Up: 手続きと継続   [Contents][Index]

6.18.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により 束縛された変数を参照できます。


Next: , Previous: , Up: 手続きと継続   [Contents][Index]

6.18.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を返します。


Next: , Previous: , Up: 手続きと継続   [Contents][Index]

6.18.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メソッドが定義されて います。正規表現を参照して下さい。

Generic Function: (setter object-apply) object argvalue

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

(set! (object arg …) value)
 ⇒ ((setter object-apply) object argvalue)

Next: , Previous: , Up: 手続きと継続   [Contents][Index]

6.18.7 継続

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

[R7RS] 現在の継続を手続き (継続手続き) にパッケージ化して、それを引数として 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の世界へ戻った時点でその連鎖が断ち切られる、と考えることができます。 そのような根無しの継続も実行することはできますが、既に失った根に戻る前に 別の箇所へと制御を移さねばなりません。他の箇所で捕まえた継続を呼んだり、 例外を投げるといったことが考えられます。

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

Macro: let/cc var body …

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

Function: dynamic-wind before body after

[R7RS] これは動的環境を管理するための基本手続きです。 動的環境とは、ある式を実行している間、維持される状態のセットのことです。 例えば「現在の出力ポート」というのは、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によって効果を戻せる 処理だけを行うべきです。例えば、(エラーを直接処理するのではなく) エラーハンドラスタックを操作する、といった具合に。


Next: , Previous: , Up: 手続きと継続   [Contents][Index]

6.18.8 多値

Function: values obj …

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

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

[R7RS] 手続き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 手続きと合わせるためです。

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)

Previous: , Up: 手続きと継続   [Contents][Index]

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

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

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

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

Function: generator-fold proc seed gen gen2 …

ジェネレータである手続き 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 …

ジェネレータ版の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

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

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

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

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


Previous: , Up: 手続きと継続   [Contents][Index]