[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

11.12 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 qualifierbody)

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

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

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

Macro: do-ec qualifier … body

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

Macro: list-ec qualifier … body

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

Macro: append-ec qualifier … body

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

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

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

Macro: vector-ec qualifier … body

[SRFI-42] Repeats body and collects the results into a vector.

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

[SRFI-42] This is like vector-ec, except that the length of the result vector is known to be k. It can be more efficient than vector-ec. Unless the comprehension repeats exactly k times, an error is signaled.

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

[SRFI-42] body must yield a numeric value. Returns sum of and product of the results, respectively.

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

[SRFI-42] body must yield a numeric value. Returns maximum and minimum value of the results, respectively. body must be evaluated at least once, or an error is signaled.

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

[SRFI-42] Evaluates test for each iteration, and returns #t as soon as it yields non-#f (for any-ec?), or returns #f as soon as it yields #f (for every?-ec). Unlink the comprehensions introduced above, these stop evaluating test as soon as the condition meets. If the qualifiers makes no iteration, #f and #t are returned, respectively.

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

[SRFI-42] First initializes the result by the value of the expression default, then start iteration, and returns the value of the first and last evaluation of body, respectively. In fact, first-ec only evaluates body at most once.

These procedures are most useful when used with control qualifiers. For example, the following first-ec returns the first set of distinct integers (x, y, z), where x*x+y*y+z*z becomes a square of another integer w.

 
(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))

Note that the first qualifier, (:integers w), generates infinite number of integers; if you use list-ec instead of first-ec it won’t stop.

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

[SRFI-42] Reduces the values produced by expr.

Suppose expr produces a sequence of values x0, x1, …, xN. Fold-ec calculates the following value:

 
(proc xN (…(proc x1 (proc x0 seed))…))

It’s similar to fold, except that proc is evaluated within the scope of qualifier … so you can refer to the variables introduced by them. On the other hand, seed is outside of the scope of qualifiers.

Fold-ec3 is almost the same but the initial value calculation. In fold-ec3, seed is only used when qualifiers makes no iteration. Otherwise it calculates the following value:

 
(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: :string vars arg1 args …

それぞれのフォームにおいて、 arg1 args …は全てリスト、ベクタ、文字列でなければなりません。 各要素をvarsに束縛して続く節を繰り返します。

 
(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

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が使われます。

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: :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
EC Qualifier: :while generator expr
EC Qualifier: :until generator expr
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 …)

Control qualifiers

EC Qualifier: if test
EC Qualifier: not test
EC Qualifier: and test …
EC Qualifier: or test …
EC Qualifier: begin command … expr
EC Qualifier: nested qualifier …

[ < ] [ > ]   [ << ] [ Up ] [ >> ]

This document was generated on July 19, 2014 using texi2html 1.82.