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

12.90 util.match - パターンマッチング

Module: util.match

このモジュールは Andrew Wright のパターンマッチングマクロライブラリを ポートしたものです。このライブラリは Scheme 界では広くつかわれており、 Chez Scheme、PLT Scheme、Scheme48、Chicken および SLIB を含む、様々な Scheme の実装にポートされています。この機能は Common Lisp の destructuring-bind に似ていますがより強力です。

この版では、オリジナルの Wright’s macro のマクロとの互換性が保たれて います。ただし、例外がふたつあって、それは、(1) box はサポートされて いません。Gauche にそれがないからです。(2) 構造のマッチングは Gauche の オブジェクトシステムに統合されています。

まず API のリストを示し、それからパターンの完全な構文のテーブルを示し、 そして例を示します。

パターンマッチング API

Macro: match expr clause …

{util.match} それぞれの clause は以下のうちどちらかです。

(pat body ...)
(pat (=> identifier) body ...)

まず、expr を各節の pat に照合します。パターンの詳しい 構文については後述します。

pat にマッチする部分が見つかれば、pat 中の パターン変数 は、expr 中の対応する要素に束縛され、その後、body … が評価されます。matchbody …の最後の式の値を返します。

節が 2つ目の形式である場合、identifierclause の失敗継続 に束縛されます。これは引数をもたない手続きで、呼ばれると、あたかも、 pat の照合に失敗したかの如くマッチャーに戻り、match が 残りの節について試行を続けます。それゆえ、body … 内部で 追加のテストを実行することが可能で、もし、満足いくものでなければ、 (identifier) を呼ぶことで、照合結果を拒絶することができます。 より詳しくは、後述の例を見てください。

どの pat もマッチしなければ、match はエラーを報告します。

Macro: match-lambda clause …

{util.match} ひとつの引数をとり、それに対して clause … を用いて、 match を実行する関数を生成します。機能としては以下の式と同等です。

(lambda (expr) (match expr clause ...))

例:

(map (match-lambda
       ((item price-per-lb (quantity 'lbs))
        (cons item (* price-per-lb quantity)))
       ((item price-per-lb (quantity 'kg))
        (cons item (* price-per-lb quantity 2.204))))
     '((apple      1.23 (1.1 lbs))
       (orange     0.68 (1.4 lbs))
       (cantaloupe 0.53 (2.1 kg))))
 ⇒ ((apple . 1.353) (orange . 0.952)
            (cantaloupe . 2.4530520000000005))
Macro: match-lambda* clause …

{util.match} match-lambda と同じですが、match をすべての引数のリスト に対して実行します。機能としては以下の式と同等です。

(lambda expr (match expr clause ...))
Macro: match-let ((pat expr) …) body-expr …
Macro: match-let name ((pat expr) …) body-expr …
Macro: match-let* ((pat expr) …) body-expr …
Macro: match-letrec ((pat expr) …) body-expr …

{util.match} 束縛部分が単なる変数ではなく、パターンを許す、一般化された letlet* および letrec です。 各 expr が評価され、その後、pat と照合され、束縛された パターン変数が body-expr … から見えるようになります。

(match-let (
             (((ca . cd) ...)   '((a . 0) (b . 1) (c . 2)))
           )
  (list ca cd))
 ⇒ ((a b c) (0 1 2))

括弧はうんざりという向きには、以下の match-let1 をおためしあれ。

Macro: match-let1 pat expr body-expr …

{util.match} これは Gauche での拡張で、オリジナルの Wright のコードにはありません。 これは以下のコードと同等です。

(match-let ((pat expr)) body-expr ...)

構文としては match-let1 は Common Lisp の destructuring-bind に非常によく似ています。

(match-let1 ('let ((var val) ...) body ...)
            '(let ((a b) (c d)) foo bar baz)
  (list var val body))
 ⇒ ((a c) (b d) (foo bar baz))
Macro: match-define pat expr

{util.match} トップレベルの define と同様ですが、変数の代りにパターンが許されます。

(match-define (x . xs) (list 1 2 3))

x  ⇒ 1
xs ⇒ (2 3)

パターンの構文

ここにあるのはパターンの構文の要約です。説明の後にあるアスタリスク (*) はオリジナルの Wright のコードにはない、Gauche の拡張で あることを意味します。

pat : patvar                       ;; 任意のオブジェクトにマッチし、patvarを束縛
    | _                            ;; 任意のオブジェクト
    | ()                           ;; 空リスト
    | #t                           ;; #t
    | #f                           ;; #f
    | string                       ;; 文字列
    | number                       ;; 数
    | character                    ;; 文字
    | keyword                      ;; キーワード (*)
    | 'sexp                        ;; S式
    | 'symbol                      ;; シンボル(S式の特殊ケース)
    | (pat1 ... patN)              ;; n 要素のリスト
    | (pat1 ... patN . patN+1)     ;; n 以上の要素を含むリスト
    | (pat1 ... patN patN+1 ooo)   ;; n 以上の要素を含むリスト、残りの各要素は
                                   ;; patN+1 にマッチしなければならない
    | #(pat1 ... patN)             ;; n 要素のベクタ
    | #(pat1 ... patN patN+1 ooo)  ;; n 以上の要素を含むベクタ、残りの各要素は
                                   ;; patN+1 にマッチしなければならない
    | ($ class pat1 ... patN)      ;; オブジェクト (patK はスロット順でマッチ)
    | (struct class pat1 ... patN) ;; 同上 (*)
    | (@ class (slot1 pat1) ...)  ;; オブジェクト (スロット名を使う) (*)
    | (object class (slot1 pat1) ...) ;; 同上 (*)
    | (= proc pat)                 ;; procを適用し、結果を pat にマッチさせる
    | (and pat ...)                ;; すべての pat にマッチするか
    | (or pat ...)                 ;; マッチする pat があるか
    | (not pat ...)                ;; どの pat もマッチしないか
    | (? predicate pat ...)        ;; predicate が真、かつ、全 pat がマッチ
    | (set! patvar)                ;; 任意のオブジェクトにマッチし、セッタを束縛
    | (get! patvar)                ;; 任意のオブジェクトにマッチし、ゲッタを束縛
    | `qp                          ;; 擬似パターン

patvar : a symbol except _, quote, $, struct, @, object, =, and, or,
         not, ?, set!, get!, quasiquote, ..., ___, ..k, __k.

ooo : ...                          ;; ゼロまたはそれ以上
    | ___                          ;; ゼロまたはそれ以上
    | ..k                          ;; k またはそれ以上。kは整数。
                                   ;;   例: ..1, ..2 ...
    | __k                          ;; k またはそれ以上。kは整数。
                                   ;;   例: __1, __2 ...

パターン例

単純な構造の分解

(match '(0 (1 2) (3 4 5))
  [(a (b c) (d e f))
   (list a b c d e f)])
 ⇒ (0 1 2 3 4 5)

述語パターンの使用

(match 123
  [(? string? x) (list 'string x)]
  [(? number? x) (list 'number x)])
 ⇒ (number 123)

let から変数と式を取り出す 反復および述語パターンの利用

(define let-analyzer
  (match-lambda
    [('let (? symbol?)
           ((var expr) ...)
       body ...)
     (format "named let, vars=~s exprs=~s" var expr)]
    [('let ((var expr) ...)
       body ...)
     (format "normal let, vars=~s exprs=~s" var expr)]
    [_
     (format "malformed let")]))

(let-analyzer '(let ((a b) (c d)) e f g))
 ⇒ "normal let, vars=(a c) exprs=(b d)"

(let-analyzer '(let foo ((x (f a b)) (y (f c d))) e f g))
 ⇒ "named let, vars=(x y) exprs=((f a b) (f c d))"

(let-analyzer '(let (a) b c d))
 ⇒ "malformed let"

= 関数適用。パターン変数 m は正規表現の適用結果にマッチする

(match "gauche-ref.texi"
  ((? string? (= #/(.*)\.([^.]+)$/ m))
   (format "base=~a suffix=~a" (m 1) (m 2))))
 ⇒ "base=gauche-ref suffix=texi"

quasipatternの例です。最初の式では、パターンのうちvalue以外の 部分がクオートされたことになり、従ってシンボルthe, answer, isはパターン変数ではなくリテラルシンボルとなります。 2番目の式がそのことを示しています。入力にあるシンボルwasは パターンのisとマッチしません。もしクオートを行わないと、 全てのシンボルはパターン変数となるので、3番目の例に示すように 任意の4つの要素を持つリストとマッチしてしまいます。

(match '(the answer is 42)
  [`(the answer is ,value) value]
  [_ #f])
 ⇒ 42

(match '(the answer was 42)
  [`(the answer is ,value) value]
  [_ #f])
 ⇒ #f

(match '(a b c d)
  [(the answer is value) value]
  [_ #f])
 ⇒ d

レコードのマッチングの例です。 次のコードは赤黒木をバランスさせる「ローテーション」操作を実装しています。

(define-record-type T #t #t
  color left value right)

(define (rbtree-rotate t)
  (match t
    [(or ($ T 'B ($ T 'R ($ T 'R a x b) y c) z d)
         ($ T 'B ($ T 'R a x ($ T 'R b y c)) z d)
         ($ T 'B a x ($ T 'R ($ T 'R b y c) z d))
         ($ T 'B a x ($ T 'R b y ($ T 'R c z d))))
     (make-T 'R (make-T 'B a x b) y (make-T 'B c z d))]
    [_ t]))


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