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

9.12 gauche.generator - ジェネレータ

Module: gauche.generator

ジェネレータは、値の列の生成器として動作する、引数を取らない手続きです。 呼ばれる度に列の次の値を返します。列の終端に達した場合はEOFが返されます。 例えば、read-charは、現在の入力ポートからの入力を一文字づつ返す ジェネレータと考えることができます。

値の列の生成源を手続きによってジェネレータとして抽象化するのは広く使われるテクニックなので、 このようなジェネレータに共通して使えるユーティリティがあると便利です。 このモジュールはそのために作られました。

SRFI-121 (Generators) はこのモジュールのサブセットです。 gauche.generatorはSRFI-121より前からあったので、 いくつかの手続きについては異なる名前を使っていました。互換性のため、 それらの手続きは両方の名前を定義しています。 SRFI-158 (Generators and accumulators)はさらに追加のジェネレータ手続きを 定義していますが、それらもこのモジュールに含まれています (ただし、アキュムレータについてはSRFI-158モジュールに分けてあります。 scheme.generator - R7RSジェネレータ参照)。

ジェネレータは、必要になったら計算を行うようなシステムを簡単に実現でき、 効率も極めて良いですが、副作用に頼った抽象化であることには注意が必要です。 例えば、途中まで列を生成した後に、最初にもどってやり直す、といったことはできません。 より関数的にオンデマンドの計算を行うためには、ジェネレータを使って 構築された、遅延シーケンスを使うのが便利です (遅延シーケンス参照)。

ジェネレータの典型的な使い方は次の通りです。まず値の源となる ジェネレータを作ります。これはジェネレータの生成手続きを使っても良いですし (ジェネレータの生成参照)、自分で一から定義しても良いでしょう。 次に、生成される値を流れの途中で加工するジェネレータ操作手続きを 必要に応じて繋いでゆきます(ジェネレータの操作参照)。 最終的には、ジェネレータから具体的な値を取り出して消費する必要があります。 そのために便利なジェネレータ消費手続きも用意してあります ジェネレータの消費参照)。このように、ジェネレータ手続きを組み合わせた パイプライン(あるいは有向非循環グラフ)を作ることで、怠惰な値伝搬ネットワークを 実現できます。


9.12.1 ジェネレータの生成

ジェネレータは特別なデータタイプではなく、普通の手続きに過ぎません。つ まり、ジェネレータはlambdaを使って作ることができます。 このモジュールは、あると便利な 良く使われるジェネレータの構築子を提供します。

自分で定義した手続きをジェネレータとして使うこともできますが、 ジェネレータを扱う手続きは、 一度EOFを返したジェネレータを繰り返して呼び出す可能性があることに注意してください。 その手続きがいったんEOFを返したら、それ以降の呼び出しに対してEOFを 返し続けるよう書かなければなりません。

ジェネレータ構築子が返すのは単なる手続きであり、そのまま印字しても 中身が何かはわかりません。この節の例では、generator->listを使って ジェネレータをリストに変換しています。generator->listの説明は、 ジェネレータの消費を参照してください。

Function: null-generator

{gauche.generator} 空ジェネレータです。呼ばれるたびにEOFオブジェクトを返します。

Function: circular-generator arg …

[SRFI-158]{gauche.generator} 与えられた引数を繰り返し生成する、無限ジェネレータを返します。

(generator->list (circular-generator 1 2 3) 10)
  ⇒ (1 2 3 1 2 3 1 2 3 1)

この例では、変換後のリストの長さを10に制限しています。そうしなければ、 generator->listは制御を返さないでしょう。

Function: giota :optional (count +inf.0) (start 0) (step 1)

{gauche.generator} iota (see リストの作成)のような、startで始まり stepずつ増加する、count個の級数のジェネレータを作成します。

(generator->list (giota 10 3 2))
  ⇒ (3 5 7 9 11 13 15 17 19 21)

startendがともに正確数であれば、ジェネレータは 正確数を生成します。そうでなければ非正確数を生成します。

(generator->list (giota +inf.0 1/2 1/3) 6)
  ⇒ (1/2 5/6 7/6 3/2 11/6 13/6)
(generator->list (giota +inf.0 1.0 2.0) 5)
  ⇒ (1.0 3.0 5.0 7.0 9.0)
Function: grange start :optional (end +inf.0) (step 1)

{gauche.generator} giotaと同様、級数ジェネレータを作成します。この級数は startに始まり、stepずつ増加してend直前まで続きます。

(generator->list (grange 3 8))
  ⇒ (3 4 5 6 7)
Function: generate proc

{gauche.generator} コルーチンからジェネレータを作成します。

引数procは、引数yieldひとつを取る手続きです。 generateは、呼ばれるとジェネレータGをただちに返します。 Gは呼ばれると、その中でyieldが呼ばれるまでprocを 実行します。yieldが呼ばれるとprocの実行は中断され、 yieldに渡した値がGから返ります。

いったんprocが返ると、それが列の終端となります—それ以降、 GはEOFオブジェクトを返します。procが返す値は無視されます。

次に挙げるコードは、0、1、2からなる級数を生成するジェネレータを作成し (事実上、(giota 3)と同じ)、gに束縛しています。

(define g
  (generate
   (^[yield] (let loop ([i 0])
               (when (< i 3) (yield i) (loop (+ i 1)))))))

(generator->list g) ⇒ (0 1 2)
Function: list->generator lis :optional start end
Function: vector->generator vec :optional start end
Function: reverse-vector->generator vec :optional start end
Function: string->generator str :optional start end
Function: uvector->generator uvec :optional start end
Function: bytevector->generator u8vector :optional start end

[SRFI-158+]{gauche.generator} 実引数の各要素を生成するジェネレータを返します。 reverse-*は、逆順で値を生成します。 SRFI-121はuvector->generator以外の手続きを定義しています。 uvector->generatorは全ての種類のユニフォームベクタを取ることができます。 一方、SRFI-121も定義するbytevector->generatoru8vectorだけを 対象とします。

(generator->list (list->generator '(1 2 3 4 5)))
  ⇒ (1 2 3 4 5)
(generator->list (vector->generator '#(1 2 3 4 5)))
  ⇒ (1 2 3 4 5)
(generator->list (reverse-vector->generator '#(1 2 3 4 5)))
  ⇒ (5 4 3 2 1)
(generator->list (string->generator "abcde"))
  ⇒ (#\a #\b #\c #\d #\e)
(generator->list (uvector->generator '#u8(1 2 3 4 5)))
  ⇒ (1 2 3 4 5)

いったん全ての要素を取り出してしまえば そのジェネレータは空になります;省略可能引数startendで ジェネレータがたどる範囲を制限することができます;startで 左の境界を、endで右の境界を指定します。

正順のジェネレータでは、最初の値としてジェネレータが生成するのは start番目の要素であり、end番目の要素の直前の要素が最後に生成する 値となります。逆順のジェネレータでは、最初の値はend番目の要素の すぐ隣の要素であり、最後の値がstart番目の要素となります。

(generator->list (vector->generator '#(a b c d e) 2))
  ⇒ (c d e)
(generator->list (vector->generator '#(a b c d e) 2 4))
  ⇒ (c d)
(generator->list (reverse-vector->generator '#(a b c d e) 2))
  ⇒ (e d c b)
(generator->list (reverse-vector->generator '#(a b c d e) 2 4))
  ⇒ (d c)
(generator->list (reverse-vector->generator '#(a b c d e) #f 2))
  ⇒ (b a)
Function: bits->generator n :optional start end
Function: reverse-bits->generator n :optional start end

{gauche.generator} これらの手続きは正確な整数を引数に取り、 bits->listと同様に(scheme.bitwise - R7RSビット演算参照)、 それを真偽値のシーケンス(0が偽、1を真)として扱います。 bits->generatorはLSBから、reverse-bits->generatorは MSBからビットを取り出します。

(generator->list (bits->generator #b10110))
 ⇒ (#f #t #t #f #t)
(generator->list (reverse-bits->generator #b10110))
 ⇒ (#t #f #t #t #f)

省略可能引数startendはビットフィールドの範囲を指定します (LSBが0)。list->generator等の同名の引数と違って、 startが右端(含まれる)、endが左端(含まれない)を指定します。 この指定方法は整数のビットフィールドにアクセスする他の手続きと一貫しています (scheme.bitwise - R7RSビット演算参照)。

(generator->list (bits->generator #x56 0 4)
  ⇒ (#f #t #t #f)  ; takes bit 0, 1, 2 and 3
(generator->list (bits->generator #x56 4 8)
  ⇒ (#t #f #t #f)  ; takes bit 4, 5, 6 and 7

(generator->list (reverse-bits->generator #x56 4 8)
  ⇒ (#f #t #f #t)  ; takes bit 7, 6, 5 and 4

註: SRFI-151のmake-bitwise-generatorbits->generatorと似ていますが、生成されるジェネレータは無限ジェネレータになります。 scheme.bitwise - R7RSビット演算参照。

Function: port->sexp-generator input-port
Function: port->line-generator input-port
Function: port->char-generator input-port
Function: port->byte-generator input-port

{gauche.generator} それぞれ文字、バイトを与えられた入力ポートから読み取るジェネレータを 返します。つまり、それぞれ (cut read input-port)(cut read-line input-port)(cut read-char input-port)(cut read-byte input-port) と同じですが、完全性のために提供されています。

Generic function: x->generator obj

{gauche.generator} 任意のコレクションobjを、objをたどるジェネレータに変換する ジェネリック関数版です。さらに、objが入力ポートである場合は、 port->char-generatorを呼びます。

Function: file->generator filename reader . open-args

{gauche.generator} ファイルfilenameをオープンし、入力ポートを引数に取る手続き readerによってそのファイルから読み取るジェネレータを返す。 引数open-argsは、open-input-fileに渡されます (ファイルポート参照)。

ファイルは、ジェネレータが尽きたところで閉じられます。最後 まで読み取る前にジェネレータが破棄された場合は、そのジェネレータ がガベージコレクタに回収されるまでファイルは開かれたままです。 特定の時点までにファイルが確実にクローズしたい場合は、with-input-from-file の動的なエクステントの中で、リーダー手続きをジェネレータとして 使うのが良いでしょう。

Function: file->sexp-generator filename . open-args
Function: file->char-generator filename . open-args
Function: file->line-generator filename . open-args
Function: file->byte-generator filename . open-args

{gauche.generator} ファイルfilenameからそれぞれS式、文字、行、バイトの列を読むジェネ レータを返します。これらは、file->generatorreader引数にreadread-charread-lineread-byteを渡して特定化したものです。

file->generator同様、open-argsはそのままopen-input-fileに 引き渡されます(ファイルポート参照)。ジェネレータが使い切られると、 ファイルは閉じられます。

Function: gunfold p f g seed :optional tail-gen

{gauche.generator} unfoldに似たジェネレータの構築子です (scheme.list - R7RSリスト参照)。

Pはシード値を引数に取り、いつ止まるかを決める述語です。 Fはシード値から値を計算する手続きです。Gは現在の シード値から次のシード値を計算する手続きです。Tail-gen は最後のシード値を取り、残りを生成するジェネレータを返します。

この関数から返されたジェネレータが呼ばれるたびに、pが現在の シード値とともに呼ばれます。これが真を返せば、やるべき事が終わっ たことがわかり、(もし与えられていたなら)tail-genが残りを 生成するジェネレータを得るために呼ばれます。さもなければ、生成する 値を得るために現在のシード値にfが適用され、シード値を更新 するためにgが使われます。

(generator->list (gunfold (^s (> s 5)) (^s (* s 2)) (^s (+ s 1)) 0))
  ⇒ '(0 2 4 6 8 10)
Function: giterate f seed
Function: giterate1 f seed

{gauche.generator} 要素が、一つ前の要素にfを適用して計算されるような値の列を無限に生成する ジェネレータを返します。giterateでは最初の値はseedそのものですが、 giterate1では(f seed)が最初の値になります。

(generator->list (giterate (pa$ * 2) 1) 10)
  ⇒ (1 2 4 8 16 32 64 128 256 512)
(generator->list (giterate1 (pa$ * 2) 1) 10)
  ⇒ (2 4 8 16 32 64 128 256 512 1024)

giterate1を別に用意しているのは、それが極めて速いからです (giterateに比べても10%ほど速いです)。

gauche.lazyliterateも参照 (gauche.lazy - 遅延シーケンスユーティリティ)。

SRFI-158 compatible procedures

Function: generator item …

[SRFI-158]{gauche.generator} item … を返すジェネレータを作成します。

Function: make-iota-generator count :optional start step

[SRFI-158]{gauche.generator} giotaとほぼ同じですが、count引数は必須です。

Function: make-range-generator start :optional end stop

[SRFI-158]{gauche.generator} grangeと同じです。

Function: make-coroutine-generator proc

[SRFI-158]{gauche.generator} generateと同じです。

Function: make-for-each-generator for-each obj

[SRFI-158]{gauche.generator} コレクションobjと、それを一要素づつ処理するfor-each手続きから、 コレクションの要素を一つづつ生成するジェネレータを作って返します。 次の定義を考えると良いでしょう。

(define (make-for-each-generator for-each coll)
  (generate (^[yield] (for-each yield coll))))

作られたジェネレータが全ての値を取り出す前にobjが変更された場合の振る舞いは、 渡されたfor-each手続きがそのケースをどのように処理するかに依存します。 それは安全かもしれないし、安全でないかもしれません。一般的に、ジェネレータが EOFを返す前にobjを変更することは避けるのが賢明です。 ジェネレータがEOFを返した後にobjを変更するのは安全です。

Function: make-unfold-generator stop? mapper successor seed

[SRFI-158]{gauche.generator} 省略可能なtail-gen引数を取らないことを除けば、 gunfoldと同じです。


9.12.2 ジェネレータの操作

以下に挙げる手続きは、どれもジェネレータ(gengen2と記述されて います)を受け取ってジェネレータを返します。便宜上、これらの手続きは gengen2としてコレクションも受けつけます;ジェネレータが 想定されているところにコレクションが渡されると、暗黙のうちにジェネレータ へと変換されるのです。

(註:これはGauche独自の拡張です。ポータブルなSRFI-121/158プログラムは、 この振る舞いに依存してはいけません。明示的にコレクションをジェネレータに 変換してください。)

Function: gcons* item … gen

[SRFI-158]{gauche.generator} genの前にitemを追加するジェネレータを返します。

(generator->list (gcons* 'a 'b (giota 2)))
 ⇒ (a b 0 1)
Function: gappend gen …

[SRFI-158]{gauche.generator} 最初に与えたジェネレータが生成する値を生成し、それが尽きたら2番目に与えた ジェネレータが生成する値を生成し、という具合に、与えられたジェネレータが 生成する値を順番に生成するジェネレータを返します。

(generator->list (gappend (giota 3) (giota 2)))
 ⇒ (0 1 2 0 1)

(generator->list (gappend))
 ⇒ ()
Function: gconcatenate gen

{gauche.generator} gen引数は、ジェネレータやシーケンスを生成するジェネレータです。 この関数は、genが生成する最初のジェネレータ/シーケンスの要素を次々に 生成し、それが尽きたら二番目のジェネレータ/シーケンスの要素を次々に生成し… というジェネレータを作って返します。

(apply gappend (generator->list gen))と似た動作ですが、 gconcatenategenが無限ジェネレータであっても動作するという 利点があります。

($ generator->list $ gconcatenate
   $ list->generator `(,(giota 3) ,(giota 2)))
 ⇒ (0 1 2 0 1)
Function: gflatten gen

[SRFI-158]{gauche.generator} 引数genはリストを生成するジェネレータです。 この手続きは、入力が生成するリストの各要素をひとつづつ生成するようなジェネレータを 作成して返します。

例: ゲームのテトリスは、次に落ちてくるピース (テトリミノ) を次の アルゴリズムで決めています: 各種類(O, I, T, S, Z, L, J)のテトリミノが 一つづつ入った袋をとり、そこからランダムに一つづつ取り出す。袋が空になったら 新たにテトリミノの袋をとり繰り返す。このアルゴリズムは次に示すとおり ジェネレータのパイプラインで実装できます。(テトリスはThe Tetris Companyの 登録商標です)

(use gauche.generator)
(use data.random) ; for permutations-of

(define g
  ($ gflatten $ permutations-of
     $ (circular-generator '(O I T S Z L J))))

(generator->list g 21)
  ⇒
  (L O Z T J S I J L Z T I O S T L Z S I J O)

この例と、上ののgconcatenateの例を比べてみてください。微妙な違いが あります。gconcatenateは、ジェネレータを生成するジェネレータを取りますが、 gflattenはリストを生成するジェネレータを取ります。

Haskell風の型表記を使うと、これら似た手続きの違いをわかりやすく整理することができるでしょう:

gappend             :: (Generator a, Generator a, ...) -> Generator a
(pa$ apply gappend) :: [(Generator a)] -> Generator a
gconcatenate        :: Generator Generator a -> Generator a
gflatten            :: Generator [a] -> Generator a
Function: gmerge less-than gen gen2 …

[SRFI-158]{gauche.generator} 入力ジェネレータから生成される要素を、手続きless-thanで決められる順 に生成するジェネレータを作って返します。 less-thanは入力ジェネレータの二つの要素a, bに対して 呼び出され、abに先行すべき時のみ#tを返します。

入力のジェネレータはそれぞれが要素を正しい順で生成しなければなりません。 そうでない場合、出力が正しい順になっていることは保証されません。

入力が一つだけ渡された場合は、(それをジェネレータへと型変換した後で)それがそのまま 返され、less-thanは呼ばれません。

(generator->list (gmerge < '(1 3 8) '(5) '(2 4)))
  ⇒ '(1 2 3 4 5 8)
Function: gmap proc gen gen2 …

[SRFI-158]{gauche.generator} 与えられたジェネレータから得られる値にprocを適用して得られる値を 生成するジェネレータを返します。返り値となるジェネレータは、引数として 与えられたジェネレータのどれかが尽きたら終了します。

注意: この手続きは、generator-map (生成された値の畳み込み参照) とは違います。generator-mapは一度に全ての値を消費し、結果をリストとして返しますが、 gmapはすぐには入力を消費せずにジェネレータを返すのです。

Function: gmap-accum proc seed gen gen2 …

{gauche.generator} 状態を持つマッピングを行うmap-accum (see コレクションに対するマッピング)のジェネレータ版 です。

引数procは、入力となるジェネレータの個数プラス1個の引数を取る 手続きで、(proc v v2 … seed)のように呼ばれます。 v, v2, … は、入力ジェネレータが生成する値であり、 seedは現在のシード値です。この手続きは2つの値を返さなければい けません。生成する値と、次のシード値です。

註:これはSRFI-121のgcombineと同じものです。

Function: gcombine proc seed gen gen2 …

[SRFI-158]{gauche.generator} gmap-accumの別名です。SRFI-121との互換性のため提供されています。

Function: gfilter pred gen
Function: gremove pred gen

[SRFI-158]{gauche.generator} ソースジェネレータgenが生成する値のうち、 predがfalseを返すもの(gfilterの場合)、 またはpredが真の値を返すもの(gremoveの場合)を 除いた値を生成するジェネレータを返します。

(generator->list (gfilter odd? (grange 0)) 6)
 ⇒ (1 3 5 7 9 11)
(generator->list (gremove odd? (grange 0)) 6)
 ⇒ (0 2 4 6 8 10)
Function: gdelete item gen :optional =

[SRFI-158]{gauche.generator} ソースジェネレータgenが生成する値のうち、itemと等しいものを 取り除いた値を生成するジェネレータを返します。 比較は省略可能引数=に渡す手続きで行われ、省略時にはequal?が使われます。

;; Note: This example relies on auto-coercing list to generator.
;; SRFI-121 requires list->generator for the second argument.
(generator->list (gdelete 3 '(1 2 3 4 3 2 1)))
  ⇒  (1 2 4 2 1)
Function: gdelete-neighbor-dups gen :optional =

[SRFI-158]{gauche.generator} ソースジェネレータgenが生成する値を生成するジェネレータを返します。 但し、連続して等しいものはそのうち1つだけが生成されます。 比較は省略可能引数=に渡す手続きで行われ、省略時にはequal?が使われます。

;; Note: This example relies on auto-coercing list to generator.
;; SRFI-121 requires string->generator for the second argument.
(generator->list (gdelete-neighbor-dups "mississippi"))
  ⇒ (#\m #\i #\s #\i #\s #\i #\p #\i)
Function: gfilter-map proc gen gen2 …

[SRFI-158]{gauche.generator} (gfilter values (gmap proc gen gen2 …)) と同様に動作しますが、若干効率的です。

Function: gstate-filter proc seed gen

[SRFI-158]{gauche.generator} この手続きは、一連の値に対するステートフルなフィルタリングを可能にします。 引数procはソースジェネレータからの値vと、シード値を取る手続きでなければ なりません。この手続きが真を返す場合に、返り値となるジェネレータは vを生成します。そうでない場合、ジェネレータはprocが真を返すか、ソース ジェネレータが尽きるまで、シード値を更新しながらprocを呼び出し続けます。

次に挙げる例は、振動する値を生成するジェネレータを受け取り、直前の値よりも 大きな値のみを生成するジェネレータを返します。

(generator->list
 (gstate-filter (^[v s] (values (< s v) v)) 0
                (list->generator '(1 2 3 2 1 0 1 2 3 2 1 0 1 2 3))))
 ⇒ (1 2 3 1 2 3 1 2 3)
Function: gbuffer-filter proc seed gen :optional tail-gen

{gauche.generator} この手続きは入力と出力がn対mで対応するようなフィルタを作ります。つまり、 入力のいくつかを見て、それに応じて1個以上の出力を生成するようなフィルタです。

手続きprocは、次の入力およびシード値を受け取り、 二つの値を返します:出力値のリスト、及び次のシード値です。 出力を決定するためにもっと入力を読むことが必要なら、 第一の返り値を()にします。

入力が終端に達したら、手続きtail-genが、その時点でのシード値を引数として 呼び出されます。tail-genは出力の終端となる値のリストを返します。 tail-genが省略された場合は、入力がなくなった時点で最後のprocが 返した出力のリストが終端となります(最後のシード値は捨てられます)

例えば、テキストファイルがあり、各行にコマンドがかかれているとしましょう。 ただし行がバックスラッシュで終わった場合、次の行へと継続しているものとみなします。 以下のコードは、入力ファイルの各行を読むジェネレータから、 論理行(継続行をくっつけたもの)をひとつづつ返すジェネレータを生成します。

(gbuffer-filter (^[v s]
                  (if-let1 m (#/\\$/ v)
                    (values '() (cons (m 'before) s))
                    (values `(,(string-concatenate-reverse (cons v s))) '())))
                '()
                (file->line-generator "input-file.txt")
                (^[s] `(,(string-concatenate-reverse s))))
Function: gtake gen k :optional padding
Function: gdrop gen k

[SRFI-158]{gauche.generator} それぞれ、ソースジェネレータgenが生成する値のうち最初からk個の値を生成する、 および最初からk個の値を除いた次の値から生成するようなジェネレータを 作成して返します。

これらの手続きはソースジェネレータがk個の値を生成する前に尽きたとしても、 何も文句を言いません。デフォルトでは、gtakeが返すジェネレータはソース ジェネレータが終端に達した時点で終了します。しかし、省略可能引数paddingを与えた 場合、返されるジェネレータは足りない分をpaddingで補うことで 常にk個の値を生成します。

註: fill引数を渡した場合、たとえ入力ジェネレータが終端に達していたとしても gtakeが返すジェネレータはk個の値を生成します。つまりその場合、 入力が使い尽くされたかどうかを判定する汎用的な方法はありません。 「入力が尽きるまでk個の要素を次々に取り出したい」という場合は 下に説明するgslicesの方が使い勝手が良いかもしれません。

互換性への註: 0.9.4までは、gtakefill?paddingの ふたつの省略可能引数を取りました。これはもともとGauche組み込みtake*に合わせて いたのですが、SRFI-121に採用されたgtakeとは非互換になりました。 SRFI-121の方が簡潔で直感的なので、0.9.5からは元のgtakeを(take*との 類似性を強調して)gtake*とリネームし、gtakeはSRFI-121に合わせることと しました。 移行をスムースにするため、gtakeは二つの省略可能引数(合計4つの引数)を取ることを 許します。その場合、gtake*が呼ばれたのと同じ動作になります。 従って、gtakeに4引数を渡している従来のコードは、0.9.5以前でも以降でも 動作します。

Function: gtake* gen k :optional fill? padding

{gauche.generator} gtakeのバリエーションで、一つの省略可能引数paddingのかわりに、 take*と同じように二つの省略可能引数を取ります (リストへのアクセスと変更参照)。 0.9.4まではこれがgtakeと呼ばれていました。 互換性のために名前を変えて残してあります。

Function: gtake-while pred gen
Function: gdrop-while pred gen

[SRFI-158]{gauche.generator} ジェネレータ版の take-whiledrop-while (リストへのアクセスと変更参照) です。 gtake-whileが返すジェネレータは、ソースジェネレータが生成する値に対して predが真を返す限り、その値を生成します。gdrop-whileが返す ジェネレータは、まずソースジェネレータから値を読み取り、その値に対して predが真を返したら、値の生成を開始します。

Function: gslices gen k :optional (fill? #f) (padding #f)

{gauche.generator} slicesのジェネレータ版です (リストへのアクセスと変更参照。 入力のジェネレータgenからk要素づつ読み出して、そのリストを生成する ジェネレータを返します。

(generator->list (gslices (giota 7) 3))
  ⇒ ((0 1 2) (3 4 5) (6))

fill?引数とpadding引数は、gtakeのそれと同様に、 入力が足りなかった場合の処理を指定します。デフォルトの、fill?#fの場合、入力がk要素に満たなければ、出力の要素も 切り詰められます (上の例参照)。fillが真の値であれば、 入力が足りない分はpaddingが当てられ、出力の最後のリストもk要素になります。

(generator->list (gslices (giota 6) 3 #t 'x))
  ⇒ ((0 1 2) (3 4 5))
(generator->list (gslices (giota 7) 3 #t 'x))
  ⇒ ((0 1 2) (3 4 5) (6 x x))
Function: ggroup gen k :optional padding

[SRFI-158]{gauche.generator} genからk要素づつ取って作ったリストを次々と生成するジェネレータを返します。 paddingが省略された時は(gslices gen k)と同じ、 paddingが与えられた時は(gslices gen k #t padding)と同じになります。

この手続きはSRFI-158で定義されたので、gslicesよりポータブルでしょう。

Function: grxmatch regexp gen

{gauche.generator} genは暗黙の変換後、文字を生成するジェネレータでなければなりません。

この手続きが返すジェネレータは、genが生成する文字のシーケンスに対して、 regexpをマッチさせようとする。そして一度マッチすると、その位置を記憶して #<rxmatch>オブジェクトを返します。さらにマッチすることがなければ、 ジェネレータは尽きます。

($ generator->list
   $ gmap rxmatch-substring
   $ grxmatch #/\w+/ "The quick brown fox jumps over the lazy dog.")
 ⇒ ("The" "quick" "brown" "fox" "jumps" "over" "the" "lazy" "dog")

注意: この手続きはgenが文字列の場合、ジェネレータへの変換がバイパスされるため 効率的です。genが文字列ではない場合、現在の実装では、genが生成する文字の シーケンス全体の長さがnの場合に、係数が小さいとはいえ、必要となるregexpの適用 回数は、O(n^2)となるかもしれません。この点について、将来改良されるかもしれませんが、 大きな入力に対してこの関数を使う場合は、注意してください。

もう一点注意: genが文字列でない場合、rxmatchはバッファリングされた 部分的な入力に対して適用されます。このため、返されたマッチのrxmatch-after は、マッチ後の“入力の残り全部”を表現しません。単にバッファの中にある文字列の 残りになります。

Function: gindex vgen igen

[SRFI-158]{gauche.generator} 引数はどちらもジェネレータです。igenは単調増加する正確な非負整数を 生成しなければなりません。

vgenから生成される値のうち、igenからの数字をインデックスとする 値のみを取り出して返すようなジェネレータを作成して返します。 どちらかの入力ジェネレータが終端に達したら、返されるジェネレータも終端に達します。

igenが条件を満たさない値を生成した場合はその時点でエラーが投げられます。

;; This example takes advantage of Gauche's auto-coercing
;; list to generator.  For portable SRFI-121 programs,
;; you need list->generator for each argument:
(generator->list (gindex '(a b c d e) '(0 2 3)))
  ⇒ (a c d)
Function: gselect vgen bgen

[SRFI-158]{gauche.generator} 引数はどちらもジェネレータです。vgenからの要素のうち、 対応するbgenの要素が真の値であるものだけを選んで返す ようなジェネレータを作成して返します。

ソースジェネレータの一つが終端に達した時に、作成された ジェネレータも終端に達します。

;; This example takes advantage of Gauche's auto-coercing
;; list to generator.  For portable SRFI-121 programs,
;; you need list->generator for each argument:
(generator->list (gselect '(a b c d e) '(#t #t #f #t #f)))
  ⇒ (a b d)

ビットジェネレータと一緒に使うと、gselectでビットマスク により要素を抽出することができます。

(generator->list (gselect '(a b c d e)
                           (reverse-bits->generator #x1a)))
  ⇒ (a b d)

9.12.3 ジェネレータの消費

いくつかのジェネレータ消費手続きは組み込みになっています。 generator-foldgenerator-fold-rightgenerator-for-eachgenerator-mapgenerator-findについては、 生成された値の畳み込みを参照してください。

Function: generator->list generator :optional k
Function: generator->reverse-list generator :optional k

[SRFI-158]{gauche.generator} generatorから項目を読み取り、それらを要素とするリストを返します (generator->reverse-listでは要素が逆順になります)。 デフォルトでは、ジェネレータを使い切るまで読み取ります。省略可能 引数kを与える場合、それは非負整数でなければならず、結果のリスト は、k個の項目を読み取るか、ジェネレータを使い切ったところで終わり となります。

無限ジェネレータを渡す時は必ずkを指定しましょう。 さもなくば、この手続きは制御を返さず、全てのメモリを食い尽して クラッシュするでしょう。

Function: generator-map->list proc gen gen2 …

[SRFI-158]{gauche.generator} procは与えられたジェネレータの数と同じだけの引数を取る手続きです。

gen gen2 …から要素をひとつづつ取ってそれらにprocを 適用した結果のリストを作って返します。 どれかのジェネレータが終端に達したらリストも終わります。

リストは積極的に作られます。全てのジェネレータが無限だと、この手続きは戻ってきません。

Function: generator->vector gen :optional k
Function: generator->string gen :optional k

[SRFI-158]{gauche.generator} genから、k要素もしくはgenが終端に達するまで要素を読み出し、 それらの要素からなるベクタまたは文字列を作って返します。

kが省略された場合はgenが終端に達するまで呼ばれますが、 genが無限ジェネレータだと返って来なくなるので注意してください。

generator->stringの場合、genが文字以外のものを生成したら エラーが報告されます。

Function: generator->uvector gen :optional k class
Function: generator->bytevector gen :optional k

[SRFI-158]{gauche.generator} ジェネレータgenから、k個の要素、あるいはgenが終端に達するまで 要素を読み出し、それらの要素からなるユニフォームベクタを作って返します。 kが省略された場合、genは常に最後まで読まれます。

classが指定された場合、それはユニフォームベクタのクラスのいずれかで なければなりません(ユニフォームベクタ参照)。省略された場合は <u8vector>が使われます。

generator->bytevectorは、class<u8vector>に固定されている 以外はgenerator->uvectorと同じです。

ジェネレータgenは、指定されたユニフォームベクタの要素となり得る数値を 生成しなければなりません。それ以外の値が生成された場合はエラーが報告されます。

Function: generator->vector! vector at gen

[SRFI-158]{gauche.generator} vectorを、インデックスatから、genが生成する値によって 埋めてゆきます。genが終端に達するか、ベクタの最後まで埋められた時点で 終了し、生成された要素の個数が返されます。

(define v (vector 'a 'b 'c 'd 'e))

(generator->vector! v 2 (giota))
  ⇒ 3

v ⇒ #(a b 0 1 2)
Function: generator->uvector! uvector at gen
Function: generator->bytevector! u8vector at gen

{gauche.generator} generator->vector!と同様、genから読み出した値で、 uvectorを、インデックスatから順に埋めてゆきます。 ベクタの終端に達するか、genが終端に達するまで続けられ、 読み出された要素の個数が戻り値となります。

generator->uvector!はどんなユニフォームベクタでも取ることができます。 generator->bytevector!u8vector限定です。

ジェネレータgenは、指定されたユニフォームベクタの要素となり得る数値を 生成しなければなりません。それ以外の値が生成された場合はエラーが報告されます。

Macro: glet* (binding …) body body2 …

{gauche.generator} これは、ジェネレータコードに頻繁に現れるモナド的なパターンを表現 するものです。and-let*の発想に似ていますが、#fのかわりにEOFを 返す式を評価すると直ちに返ります。

binding部分は(var expr)( expr )の形をとります。 実際の定義を見れば、この構文が明解に理解できるでしょう。

(define-syntax glet*
  (syntax-rules ()
    [(_ () body body2 ...) (begin body body2 ...)]
    [(_ ([var gen-expr] more-bindings ...) . body)
     (let1 var gen-expr
       (if (eof-object? var)
         var
         (glet* (more-bindings ...) . body)))]
    [(_ ([ gen-expr ] more-bindings ...) . body)
     (let1 var gen-expr
       (if (eof-object? var)
         var
         (glet* (more-bindings ...) . body)))]))
Macro: glet1 var expr body body2 …

{gauche.generator} これとglet*の関係は、let1let*のそれと同じです。言葉を 変えれば、これは(glet* ([var expr]) body body2 …)と同じです。

Macro: do-generator (var gexpr) body …

{gauche.generator} これは、ジェネレータ版のdolistでありdotimesです(変数束縛参照)。

gexprはジェネレータを生成する式であり、一度だけ評価されます。 結果となるジェネレータはEOFを返すまで繰り返し呼ばれます。ジェネレータ が呼ばれる毎に、ジェネレータが生成する値に束縛されるvarの スコープ内でbody …が評価されます。

dolistdotimesがそうであるように、このマクロも副作用のため のものです。同じことはfor-each族を使っても書けますが、このマクロを 使って命令的に記述したコードの方が読みやすいこともあるのです。

(do-generator [line (file->line-generator "filename")]
  ;; lineを使って何か副作用のある作業をする
  )
Function: generator-any pred gen
Function: generator-every pred gen

[SRFI-158]{gauche.generator} anyevery (see リストをたどる手続き) と同じですが、 ジェネレータに対して使えます。

Function: generator-count pred gen

[SRFI-158]{gauche.generator} genが生成する要素ののうち、predを満たすものの個数を返します。 副作用としてgenは使い尽くされます。

Function: generator-unfold gen unfold arg …

[SRFI-158]{gauche.generator} genが生成する値をseedとしてunfoldを適用します。次の式と同じです:

(unfold eof-object? identity (^_ (gen)) (gen) arg …)

unfold手続きは、 scheme.listモジュールのunfold(see scheme.list - R7RSリスト参照)のように、 次のとおり引数を取らなければなりません: (unfold stop? mapper successor seed arg …)

これはジェネレータをシーケンスに変換する一般的な方法と見ることができます。 x->generatorの反対です。

(generator-unfold (x->generator "abc") string-unfold)
  ⇒ "abc"


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