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

11.11 srfi.42 - 先行評価的内包表記

Module: srfi.42

このモジュールはジェネリックな内包表記(comprehension)機構を提供します。 この機構は他の言語(Haskell、Pythonなど)では組み込みの機構になっていま す。この機構は豊富な操作手続を提供しているので、リストジェネレータとい うだけではなく、ジェネリックなループ構文(Common Lisp の loop マ クロ並みに強力/邪悪だという人もいます)を提供しています。

この機構は名前の通り、先行評価的に走ります。すなわち、リストを生成する場合、評価時 にすべてのリストを生成します。要素を要求駆動的に生成するわけで はありません。それゆえ、無限列を表現することはできません。それが自然に できる Haskell とは違います。Gaucheは、遅延生成される有限/無限列を 扱う方法を他にいくつか提供しています。 遅延シーケンスgauche.generator - ジェネレータutil.stream - ストリームライブラリを 参照してください。

先行評価的内包表記の例

いくつかの例からはじめましょう。

5番目までの整数の自乗のリストを生成しましょう。

(list-ec (: i 5) (* i i)) ⇒ (0 1 4 9 16)

list-ecはリストを生成する内包表記マクロです。 最初のフォーム(: i 5)qualifierと呼ばれ、 繰り返しを行う値の集合を指定します (この例では0以上5未満の整数)。 最後のフォーム(* i i)bodyと呼ばれ、 qualifierによって生成される値それぞれにつき評価される通常のScheme式です。

内包表記は複数のqualifierを持つことができます。 次の例は数の対(x y)の集合を生成します。ここでxは 2以上 5未満、yは1以上 x 未満です。

(list-ec (: x 2 5) (: y 1 x) (list x y))
  ⇒ ((2 1) (3 1) (3 2) (4 1) (4 2) (4 3))

複数のqualifierはネストするように動作します。つまり、(: x 2 5)は 残りの節—(: y 1 x) および (list x y) を繰り返すように 指定しているということです。

上の2つの例はHaskellで書くと以下のようになります。

[ i*i   | i <- [0..4] ]
[ (x,y) | x <- [2..4], y <- [1..x-1] ]

違いに注意:(1) Haskellでは要素になる本体部が先にきて、そのあとに修飾 部(セレクタ)がきます。SRFI-42では本体部は最後になります。(2) SRFI-42で は範囲指定の下限はそれを含み、上限はそれを含みません。

a^3+b^3 = c^3+d^3を満すような数字の集合(a b c d)を列挙し ましょう。

(define (taxi-number n)
  (list-ec (: a 1 n)
           (: b (+ a 1) n)
           (: c (+ a 1) b)
           (: d (+ c 1) b)
           (if (= (+ (expt a 3) (expt b 3))
                  (+ (expt c 3) (expt d 3))))
           (list a b c d)))

複数の変数を(ネストするのではなく)同時に変化させたい場合は、 複数のqualifierを次のようにまとめることができます。

(list-ec (:parallel (: x '(a b c d)) (: y '(1 2 3 4)))
         (list x y))
  ⇒ ((a 1) (b 2) (c 3) (d 4))

リストだけではなく、他のシーケンスも生成できます。

(vector-ec (: i 5) i) ⇒ #(0 1 2 3 4)
(string-ec (: i 5) (integer->char (+ i 65))) ⇒ "ABCDE"

畳み込み演算も適用できます。

(sum-ec (: i 1 100) i)
  ⇒ 4950    ;; 1以上100未満の整数の和
(product-ec (: i 1 10) i)
  ⇒ 362880 ;;  1以上10未満の整数の積

内包表記マクロ

それぞれの内包表記は以下のような形式になります。

(comprehension-macro qualifier ... body)

qualifier …の指定に従ってbodyをくりかえし評価します。内包表記の種類 によって、bodyの結果は(リスト、ベクタ、文字列などに)集約されるか、 (sum、product、min、maxなどによって)畳み込まれるか、あるいは、単に捨て られます。

それぞれのqualifierは、それ以降のqualifierbody をどのように繰り返すかを指定します。qualifierには、繰り返しに 使う値を生成する生成的qualifierと、条件によって値を繰り返しから 省く制御的qualifierがあります。以下のQualifiersの節を参照してください。

いくつかの内包表記では、追加の値がqualifiersの前か、body の後に置かれます。

Macro: do-ec qualifier … body

[SRFI-42]{srfi.42} bodyを繰り返します。bodyの返り値は捨てられます。 この形式は副作用目的で使います。

Macro: list-ec qualifier … body

[SRFI-42]{srfi.42} bodyを繰り返し、結果をリストに集めて返します。

Macro: append-ec qualifier … body

[SRFI-42]{srfi.42} bodyを繰り返し、その結果のリストを結合して返します。 bodyは必ずリストを返さなければなりません。

Macro: string-ec qualifier … body
Macro: string-append-ec qualifier … body

[SRFI-42]{srfi.42} bodyを繰り返します。bodystring-ecでは文字へ、 string-append-ecでは文字列へと評価されなければなりません。 結果を集め、または結合した文字列が買えされます。

Macro: vector-ec qualifier … body

[SRFI-42]{srfi.42} bodyを繰り返し、結果をベクタに集めて返します。

Macro: vector-of-length-ec k qualifier … body

[SRFI-42]{srfi.42} vector-ecと似ていますが、最終的なベクタの長さがkと分かっている時に 使えます。あらかじめベクタをアロケートしておけるのでvector-ecより効率が良いです。 繰り返しが正確にk回にならなかった場合はエラーが投げられます。

Macro: sum-ec qualifier … body
Macro: product-ec qualifier … body

[SRFI-42]{srfi.42} bodyは数値を生成しなければなりません。繰り返しによって生成される数値の和と積を それぞれ返します。

Macro: min-ec qualifier … body
Macro: max-ec qualifier … body

[SRFI-42]{srfi.42} bodyは実数値を生成しなければなりません。 最初および最大の結果をそれぞれ返します。bodyは最低1回は実行されねばならず、 そうでなければエラーが投げられます。

Macro: any?-ec qualifier … test
Macro: every?-ec qualifier … test

[SRFI-42]{srfi.42} 繰り返しのたびにtestを評価し、any?-ecではtestが偽でない 値を返したら直ちにその値を、every?-ecではtestが偽を返したら 直ちに#fを、それぞれ返します。これまで上で説明してきた 内包表記とは異なり、これらはtestが条件を満たしたら繰り返しを打ち切ります。 もし繰り返しが一度もなされなかった場合、それぞれ#f#tが戻り値となります。

Macro: first-ec default qualifier … body
Macro: last-ec default qualifier … body

[SRFI-42]{srfi.42} まず結果をdefault式の値で初期化し、繰り返しを開始します。 そして、最初または最後のbodyの値をそれぞれ返します。 実際のところ、first-ecbodyをたかだか一回しか評価しません。

これらのフォームは、フロー制御のqualifierと一緒に使うことで効果を発揮します。 例えば、下のfirst-ecは、それぞれ異なる3つの正整数(x, y, z)で、 x*x+y*y+z*zwの平方と等しくなるような 組み合わせのうち最初のものを返します。

(first-ec #f (:integers w) (: z 1 w) (: y 1 z) (: x 1 y)
          (if (= (* w w) (+ (* x x) (* y y) (* z z))))
          (list x y z w))

(:integers w)は無限に整数列を生成することに注意してください。 first-ecのかわりにlist-ecを使うと、評価は終了しません。

Macro: fold-ec seed qualifier … expr proc
Macro: fold3-ec seed qualifier … expr init proc

[SRFI-42]{srfi.42} exprが生成する値をまとめます。

exprが生成する値をx0, x1, …, xNとした場合、 fold-ecは次の値を計算します:

(proc xN (...(proc x1 (proc x0 seed))...))

foldと似ていますが、procqualifier …のスコープ内で 評価されるので、それらが導入する変数を参照することができます。 ただしseedqualifierのスコープの外で評価されます。

fold-ec3は初期値の計算以外は同じです。 fold-ec3では、seedqualifierが一度も繰り返しを行わなかった 場合にのみ使われます。そうでなければ以下の値が計算されます:

(proc xN (...(proc x1 (init x0))...))

Qualifiers

生成的qualifier

このタイプのqualifierは、いくつかの値(無限個のこともあります)を次々に 生成し、各値について残りの節を繰り返します。

以下の説明において、varsというのはひとつの識別子か、 識別子とフォーム(index identifier2)の並びです。 最初の形式での識別子、あるいは二番目の形式での最初の識別子は、 生成された値が束縛される変数です。二番目の形式のidentifier2は、 0から始まり値が生成されるたびにインクリメントされる整数値が束縛されます。 次の例を見てください。

(list-ec (: x '(a b c)) x)
  ⇒ (a b c)
(list-ec (: x (index y) '(a b c)) (cons x y))
  ⇒ ((a . 0) (b . 1) (c . 2))
EC Qualifier: : vars arg1 args …

arg1 args … の型に基づいて、以下の生成的qualifierの いずれかにディスパッチされる、汎用的なqualifierです。

EC Qualifier: :list vars arg1 args …
EC Qualifier: :vector vars arg1 args …
EC Qualifier: :uvector vars arg1 args …
EC Qualifier: :string vars arg1 args …

それぞれのフォームにおいて、 arg1 args …は全てリスト、ベクタ、ユニフォームベクタ、 あるいは文字列でなければなりません。 各要素をvarsに束縛して続く節を繰り返します。 (:uvectorはGaucheの独自拡張です。)

(list-ec (:string c "ab" "cd") c) ⇒ (#\a #\b #\c #\d)

汎用qualifier:に渡された引数が全てリスト、ベクタ、ユニフォームベクタあるいは文字列 であった場合は、これらのqualifierが使われます。

EC Qualifier: :integers vars

0から始まり1づつ増加しつづける無限正確整数列を生成します。

EC Qualifier: :range vars stop
EC Qualifier: :range vars start stop
EC Qualifier: :range vars start stop step
EC Qualifiter: :range vars range

最初の3つの形式は、 startから始まり、stepづつ増加し、stopを越えないような 正確な整数列を生成します。startが省略された場合は0、 stepが省略された場合は1が使われます。 stepに負数を与えれば減少列も作れます。

(list-ec (:range v 5) v)      ⇒ (0 1 2 3 4)
(list-ec (:range v 3 8) v)    ⇒ (3 4 5 6 7)
(list-ec (:range v 1 8 2) v)  ⇒ (1 3 5 7)
(list-ec (:range v 8 1 -2) v) ⇒ (8 6 4 2)

汎用qualifier:に1個から3個の正確な整数が与えられた場合は、 このqualifierが使われます。

最後の形式のように、レンジオブジェクト (data.range - レンジ参照) が与えられた場合は、 そのレンジの要素を順に生成します。

EC Qualifier: :real-range vars stop
EC Qualifier: :real-range vars start stop
EC Qualifier: :real-range vars start stop step

startから始まり、stepづつ増加し、stopを越えないような 実数列を生成します。startが省略された場合は0、 stepが省略された場合は1が使われます。 全ての引数が正確数であれば正確な数列が、ひとつでも非正確数が混じって入れば 非正確な数列が生成されます。

(list-ec (:real-range v 5.0) v)
  ⇒ (0.0 1.0 2.0 3.0 4.0)
(list-ec (:real-range v 1 4 1/3) v)
  ⇒ (1 4/3 5/3 2 7/3 8/3 3 10/3 11/3)
(list-ec (:real-range v 1 5.0 1/2) v)
  ⇒ (1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5)

汎用qualifier:に1個から3個の実数が与えられ、そのうちどれかひとつでも 正確な整数でないものがあれば、このqualifierが使われます。

EC Qualifier: :char-range vars min max

文字minからmaxまで(両端含む)の文字を順に生成します。 文字順はchar<=?で比べられるのと同じ順になります (文字参照)。

(list-ec (:char-range v #\a #\e) v)
  ⇒ (#\a #\b #\c #\d #\e)

汎用qualifier:に2つの文字が与えられた場合は、 このqualifierが使われます。

EC Qualifier: :port vars port
EC Qualifier: :port vars port read-proc

入力ポートportから、read-procを使って読まれる値を、 EOFに出会うまで次々に生成します。read-procが省略された場合はread が使われます。

(call-with-input-string "a \"b\" :c"
  (^p (list-ec (:port v p) v)))
  ⇒ (a "b" :c)

汎用qualifier:に2つの引数が与えられ、最初の引数が入力ポートであれば、 このqualifierが使われます。

EC Qualifier: :generator vars gen

これはSRFI-42には定義さていない、Gauche独自の拡張です。 genは引数と取らない手続きです。このqualifierは genがEOFを返すまで繰り返します。

Gaucheは、そのような手続きを作ったり操作したりする便利な関数群を 提供しています。gauche.generator - ジェネレータを参照してください。

(use gauche.generator)
(list-ec (:generator v (grange 1 8)) v)
  ⇒ (1 2 3 4 5 6 7)

汎用qualifier:に1つの引数が与えられ、それが引数無しで呼び出し可能なものであれば、 このqualifierが使われます。

EC Qualifier: :collection vars coll

これはSRFI-42には定義さていない、Gauche独自の拡張です。 coll<collection>クラスかそのサブクラスのインスタンスでなければなりません (gauche.collection - コレクションフレームワーク参照)。 このqualifierはコレクションの各要素に対して繰り返します。

特定の型に特化したqualifierがある場合はそちらを使う方が速いです (例えば、ベクタはコレクションでもありますが、:collectionを使うより :vectorを使う方が速いです。)

汎用qualifier:に1つの引数が与えられ、 それがSRFI-42で用意されている型以外のコレクションであれば、 このqualifierが使われます。

EC Qualifier: :parallel generator …

複数のジェネレータ節を並列に走査するのに使います。 generatorのどれかが値を使い切った時点で止まります。

(list-ec (:parallel (: x '(a b c))
                    (: y "defg"))
  (cons x y))
 ⇒ ((a . #\d) (b . #\e) (c . #\f))

;; Compare with this:
(list-ec (: x '(a b c))
         (: y "defg")
  (cons x y))
 ⇒ ((a . #\d) (a . #\e) (a . #\f) (a . #\g)
     (b . #\d) (b . #\e) (b . #\f) (b . #\g)
     (c . #\d) (c . #\e) (c . #\f) (c . #\g))
EC Qualifier: :let vars expr

exprを評価し、その値をvarsに束縛して1回だけ以降の節を実行します。 varsにインデックス変数があればそれは0に束縛されます。 実効的には(:list vars (list expr))と同じです。

EC Qualifier: :while g-qualifier expr
EC Qualifier: :until generator expr

g-qualifierは生成的qualifierです。g-qualifierが生成する値によって exprが真の値である間/真の値になるまで、値を生成しつづけます。expr内では g-qualifierが束縛する変数が使えます。

(use math.prime)
(sum-ec (:while (: p (index k) *primes*) (<= k 100)) p)
EC Qualifier: :dispatched vars dispatch arg1 args …
EC Qualifier: :do (lb …) ne1? (ls …)
EC Qualifier: :do (let (ob …) oc …) (lb …) ne1? (let (ib …) ic …) ne2? (ls …)

フロー制御qualifier

EC Qualifier: if test

testを評価し、それが偽だった場合はその回の繰り返しを中止し、次の繰り返しに移ります。

次の例は、a^2 + b^2 = c^2を満たすピタゴラス数(a b c)の組を、 c < 100 の範囲で列挙します。

(list-ec (: c 1 100) (: a 1 c) (: b a c)
         (if (= (square c) (+ (square a) (square b))))
         (list a b c))
  ⇒ ((3 4 5) (6 8 10) (5 12 13) (9 12 15) (8 15 17) ...)
EC Qualifier: not test
EC Qualifier: and test …
EC Qualifier: or test …

それぞれ(if (not test))(if (and test …))(if (or test …))の略記です。

EC Qualifier: begin command … expr

繰り返しの途中に、exprを評価する前にcommand …を副作用のために評価します。 commandの結果は捨てられます。

EC Qualifier: nested qualifier …

qualifier …をこの箇所にスプライシングします。たとえば (list-ec (: a 2) (nested (: b 2) (: c 2)) (: d 2) (list a b c d))(list-ec (: a 2) (: b 2) (: c 2) (: d 2) (list a b c d)) と同じです。



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