本節では、構造的なマッチングを容易にする組み込みパターンの集合であるバッククォート形式のパターンについて説明します。背景については、パターンマッチングの条件式を参照してください。
Backquoteスタイルのパターンは、pcaseパターン拡張(pcase-defmacroを用いて作成)の強力なセットであり、expvalをその構造の仕様に容易にマッチさせることができます。
例えば、第1要素が特定の文字列で、第2要素が任意の値となる2つの要素からなるリストでなければならない expval にマッチさせるには、次のようなコアパターンを記述できます。
(and (pred listp)
ls
(guard (= 2 (length ls)))
(guard (string= "first" (car ls)))
(let second-elem (cadr ls)))
あるいは、同等のバッククォート形式のパターンを記述することもできます:
`("first" ,second-elem)
バッククォート形式のパターンの方が簡潔で、expvalの構造に似ており、lsのバインドも避けられます。
バッククォート形式のパターンは `qpat
という形式をとり、qpat は以下の形式をとり得ます:
(qpat1 . qpat2)
expval がコンスセルであり、かつその car が
qpat1 に、cdr が qpat2 にそれぞれ一致する場合にマッチします。これは (qpat1 qpat2 …) のようにリストへも容易に一般化できます。
[qpat1 qpat2 … qpatm]expval が長さ m のベクトルであり、その 0..(m-1) 番目の要素がそれぞれ qpat1、qpat2 … qpatm
と一致する場合にマッチします。
symbolkeywordnumberstringexpval の対応する要素が指定されたリテラルオブジェクト equal と一致する場合にマッチします。
,patternexpval の対応する要素が pattern
に一致する場合にマッチします。なお、pattern は pcase がサポートする任意の種類となります。(上記の例では、second-elem は symbol
コアパターンであるため、あらゆるものにマッチし、second-elem を let-bind
します。)
対応要素とは、backquote-style パターンにおいて、qpat の構造的位置と同じ構造的位置にある expval
の部分のことです。(上記の例では、second-elem の対応要素は expval の2番目の要素となります。)
以下に、pcase を用いて簡単な式言語のインタプリタを実装する例を示します(なお、fn 節内のラムダ式が body と arg を適切にキャプチャするには、字句束縛が必要です(字句束縛 を参照)):
(defun evaluate (form env)
(pcase form
(`(add ,x ,y) (+ (evaluate x env)
(evaluate y env)))
(`(call ,fun ,arg) (funcall (evaluate fun env)
(evaluate arg env)))
(`(fn ,arg ,body) (lambda (val)
(evaluate body (cons (cons arg val)
env))))
((pred numberp) form)
((pred symbolp) (cdr (assq form env)))
(_ (error "Syntax error: %S" form))))
最初の3つの節では、バッククォート形式のパターンが使用されています。`(add ,x
,y)は、formがリテラルシンボルaddで始まる3要素のリストであることを確認し、2番目と3番目の要素を抽出してそれぞれシンボルxとyにバインドするパターンです。これは分解として知られており、pcaseパターンによる分解を参照してください。節の本体ではxとyを評価し、その結果を加算します。同様に、call節は関数呼び出しを、fn節は無名関数の定義を実装しています。
残りの節ではコアパターンが使用されています。(pred numberp)は、formが数値である場合にマッチします。マッチすると、本体がそれを評価します。(pred symbolp)は、formがシンボルである場合にマッチします。マッチすると、本体はenvからそのシンボルを検索し、その関連付けを返します。最後に、_は何でもマッチするキャッチオールパターンであるため、構文エラーの報告に適しています。
この小さな言語によるサンプルプログラムと、その評価結果をいくつか挙げます:
(evaluate '(add 1 2) nil) ⇒ 3 (evaluate '(add x y) '((x . 1) (y . 2))) ⇒ 3 (evaluate '(call (fn x (add 1 x)) 2) nil) ⇒ 3 (evaluate '(sub 1 2) nil) ⇒ error