pcase マクロ ¶背景については、パターンマッチング条件式を参照してください。
clausesの各節は、(pattern body-forms…)という形式である。
expressionを評価してその値expvalを求める。clausesの中で、patternがexpvalと一致する最初の節を探し、その節のbody-formsに制御を移す。
マッチした場合、pcase の値は、成功節における body-forms の最後の値となります。そうでない場合、pcase は nil と評価されます。
各 pattern は pcase パターン
である必要があり、以下で定義されているコアパターンのいずれか、または pcase-defmacro
経由で定義されているパターンのいずれかを使用できます(pcase の拡張 を参照)。
本小節の残りの部分では、コアパターンのさまざまな形式について説明し、いくつかの例を提示した上で、一部のパターン形式で提供されるlet束縛機能を利用する際の重要な注意点を述べて締めくくります。コアパターンには、以下のような形式があります。
_ (underscore)任意の expval に一致します。これは ドントケア または ワイルドカード とも呼ばれます。
'valexpval が val と等しい場合に一致します。比較は
equal によって行われるものとみなされます(等価述語を参照)。
keywordintegerstringexpval がリテラルオブジェクトと等しい場合にマッチします。これは前述の 'val
の特殊なケースであり、これらの型のリテラルオブジェクトが自己クォート可能であるため実現しています。
symbol任意の expval にマッチし、さらに symbol を expval に let-bind することで、このバインドを body-forms で利用できるようにします(動的バインド を参照)。
symbol がシーケンシングパターン seqpat
の一部である場合(例:後述の and を使用する場合)、symbol が出現した後の seqpat
の部分に対してもバインドが可能です。この用法にはいくつかの注意点があるため、注意点を参照してください。
避けるべき2つのシンボルは、_(前述)と同様に動作し非推奨となっているtと、エラーを示すnilです。同様に、キーワードシンボルをバインドすることに意味はありません(決して変化しない変数を参照)。
`qpatバッククォート形式のパターン。詳細はバッククォート形式のパターンを参照してください。
(cl-type type)expval が type 型(cl-typep で認められる型記述子。Common Lisp
Extensions の Type
Predicates を参照)である場合にマッチします。例:
(cl-type integer) (cl-type (integer 0 10))
(pred function)述語 function を expval
に対して呼び出した際に nil 以外が返される場合に一致します。この判定は (pred (not function))
という構文で否定できます。述語 function は、以下のいずれかの形式を取り得ます:
指定された関数を、引数 expval を1つ渡して呼び出します。
例: integerp
匿名関数を1つの引数 expval で呼び出します(ラムダ式を参照)。
例: (lambda (n) (= 42 n))
関数(関数呼び出しの第1要素)を、n個の引数(その他の要素)と、追加のn+1番目の引数であるexpvalを用いて呼び出してください。
例: (= 42)
この例では、関数は =、n は 1
であり、実際の関数呼び出しは (= 42 expval) となります。
_ 引数を伴う関数呼び出し指定された引数(その他の要素)を用い、_をexpvalに置き換えて、関数(関数呼び出しの第1要素)を呼び出してください。
例: (gethash _ memo-table) この例では、関数は gethash であり、実際の関数呼び出しは (gethash expval memo-table) となります。
(app function pattern)expval に対して function を呼び出した結果が
pattern と一致する場合にマッチします。function は、前述の pred
で説明されている形式のいずれかを取ることができます。ただし、pred とは異なり、app はブール値ではなく、結果を pattern と照合します。
(guard boolean-expression)boolean-expression が nil
以外で評価される場合に一致します。
(let pattern expr)exprを評価してexprvalを得て、exprvalがpatternにマッチすればマッチする。 (patternがsymbolを用いてシンボルを値に束縛できるため、letと呼ばれる。)
シーケンシングパターン(seqpatとも呼ばれる)とは、サブパターンの引数を順次処理するパターンのことです。pcaseには、andとorの2種類があります。これらは、同名の特殊形式(条件を組み合わせるための構文を参照)と同様の動作をしますが、値を処理する代わりにサブパターンを処理します。
(and pattern1…)pattern1…を順に照合し、いずれかが一致しなくなるまで試みます。その場合、同様にandも一致しなくなり、残りの部分パターンは検証されません。すべての部分パターンが一致すれば、andが一致します。
(or pattern1 pattern2…)pattern1、pattern2、…
を順にマッチさせ、いずれかが成功するまで試行します。その場合、orも同様にマッチし、残りの部分パターンは検証されません。
body-forms に一貫した環境(評価入門を参照)を提示し(これによりマッチ時の評価エラーを回避する)、パターンによって束縛される変数の集合は、各サブパターンによって束縛される変数の和集合となる。マッチしたサブパターンによって変数が束縛されていない場合、その変数は
nil に束縛される。
(rx rx-expr…)rx 正規表現表記(rx
構造化正規表現表記を参照)を用いて、rx-expr… という文字列を、あたかも
string-match であるかのように照合します。
通常の rx 構文に加え、rx-expr… には以下の構成要素を含めることができます。
(let ref rx-expr…)シンボル ref を、rx-expr... にマッチするサブマッチにバインドします。ref は
body-forms においてサブマッチまたは nil
の文字列にバインドされますが、backref でも使用できます。
(backref ref)標準的な backref 構文と同様ですが、ここでは ref に、直前の (let ref …) 構文で導入された名前を指定することも可能です。
cl-case ¶ に対する優位性以下に、pcase が cl-case
に対して持ついくつかの利点を示す例を挙げます(Common Lisp Extensions の Conditionals
を参照してください)。
(pcase (get-return-code x) ;; string ((and (pred stringp) msg) (message "%s" msg))
;; symbol
('success (message "Done!"))
('would-block (message "Sorry, can't do it now"))
('read-only (message "The schmilblick is read-only"))
('access-denied (message "You do not have the needed rights"))
;; default (code (message "Unknown return code %S" code)))
cl-case を使用する場合、get-return-code の戻り値を保持するためのローカル変数 code を明示的に宣言する必要があります。また、cl-case は比較に eql
を使用するため、文字列では扱いにくいです。
and ¶ の使用よく使われる慣用表現として、and で始まるパターンを記述し、1つ以上の symbol
サブパターンで、後続のサブパターン(およびボディ形式)へのバインドを定義する方法があります。例えば、以下のパターンは1桁の整数にマッチします。
(and
(pred integerp)
n ; bind n to expval
(guard (<= -9 n 9)))
まず、(integerp expval)
の評価結果が nil 以外であれば pred
がマッチします。次に、n は任意の文字列にマッチし、n を expval にバインドする symbol パターンです。最後に、ブール式 (<= -9 n 9)(n への参照に注意)の評価結果が nil 以外であれば
guard がマッチします。これらのサブパターンがすべてマッチすれば、and がマッチします。
pcase ¶ を用いた再定式化シンプルなマッチングタスクを、従来の実装(関数 grok/traditional)から pcase を用いた実装(関数 grok/pcase)へと書き換える方法を示す別の例を挙げます。これら両方の関数のドキュメント文字列は、“OBJ
が "key:NUMBER" 形式の文字列である場合、NUMBER(文字列)を返す。そうでない場合は、リスト(デフォルトは
"149")を返す。” となっています。まず、従来の実装(正規表現を参照)は以下の通りです:
(defun grok/traditional (obj)
(if (and (stringp obj)
(string-match "^key:\\([[:digit:]]+\\)$" obj))
(match-string 1 obj)
(list "149" 'default)))
(grok/traditional "key:0") ⇒ "0"
(grok/traditional "key:149") ⇒ "149"
(grok/traditional 'monolith) ⇒ ("149" default)
再定式化により、symbol の束縛に加え、or、and、pred、app、および let の束縛も示される。
(defun grok/pcase (obj)
(pcase obj
((or ; line 1
(and ; line 2
(pred stringp) ; line 3
(pred (string-match ; line 4
"^key:\\([[:digit:]]+\\)$")) ; line 5
(app (match-string 1) ; line 6
val)) ; line 7
(let val (list "149" 'default))) ; line 8
val))) ; line 9
(grok/pcase "key:0") ⇒ "0"
(grok/pcase "key:149") ⇒ "149"
(grok/pcase 'monolith) ⇒ ("149" default)
grok/pcase の大部分は pcase
形式の単一の節であり、1〜8行目がパターン、9行目が(単一の)ボディ形式となっています。パターンは or であり、引数のサブパターンを順に照合しようとします。まず and(2〜7行目)、次に let(8行目)という順で、いずれかがマッチするまで試行します。
前述の例(例1を参照)と同様に、andはpredサブパターンで始まっており、これにより後続のサブパターンが正しい型(この場合は文字列)のオブジェクトで動作するようになっています。(stringp expval)がnilを返すと、predが失敗し、結果としてandも失敗します。
次の pred(4〜5行目)は (string-match RX expval) を評価し、その結果が nil
以外であればマッチします。つまり、expval が期待通りの形式である key:NUMBER であることを意味します。ここでも条件を満たさない場合、pred も and も失敗します。
最後に(このandサブパターンの系列において)、appは(match-string 1 expval)(6行目)を評価して一時値tmp(すなわち「NUMBER」という部分文字列)を取得し、tmpをパターンval(7行目)にマッチさせようとします。これはsymbolパターンであるため、無条件にマッチし、さらにvalをtmpにバインドします。
app がマッチしたため、すべての and
サブパターンがマッチし、したがって and もマッチします。同様に、and がマッチすると or もマッチし、サブパターン
let の試行には進みません(8行目)。
obj
が文字列ではない、あるいは文字列であっても形式が正しくない場合を考えてみましょう。この場合、pred(3〜5行目)のいずれかがマッチせず、その結果 and(2行目)もマッチせず、さらに or(1行目)がサブパターン let(8行目)の試行へと進みます。
まず、let は (list "149" 'default) を評価して ("149" default) と exprval を得て、次に exprval をパターン val にマッチさせようとします。これは symbol
パターンであるため、無条件にマッチし、さらに val を exprval にバインドします。これで let
がマッチしたため、or もマッチします。
and と let
の両方のサブパターンが、symbol パターン val
へのマッチを試み(常に成功し)、その過程で val
をバインドするという、同じ方法で終了している点に注目してください。したがって、or
は常にマッチし、制御は常にボディフォーム(9行目)へと移ります。それが正常にマッチした pcase
節における最後のボディフォームであるため、それが pcase の値となり、同様に grok/pcase の戻り値となります(関数とは何か? を参照)。
前述の例はいずれも、何らかの形でsymbolサブパターンを含むシーケンスパターンを使用しています。その用法に関する重要な詳細を以下に示します。
eq
を用いた等価判定に展開されます。
以下の例では、2つの節と2つのseqpat(AとB)を持つpcase形式が示されています。AとBはともに、まずexpvalがペアであるかを確認し(predを使用)、次にexpvalのcarとcdrにシンボルをバインドします(それぞれ1つのappを使用)。
Aでは、記号 st が2回言及されているため、2回目の言及は eq を用いた等価判定となる。一方、Bでは s1 と
s2 という2つの別々の記号が用いられており、いずれも独立した束縛となる。
(defun grok (object)
(pcase object
((and (pred consp) ; seqpat A
(app car st) ; first mention: st
(app cdr st)) ; second mention: st
(list 'eq st))
((and (pred consp) ; seqpat B
(app car s1) ; first mention: s1
(app cdr s2)) ; first mention: s2
(list 'not-eq s1 s2))))
(let ((s "yow!")) (grok (cons s s))) ⇒ (eq "yow!") (grok (cons "yo!" "yo!")) ⇒ (not-eq "yo!" "yo!") (grok '(4 2)) ⇒ (not-eq 4 (2))
and、symbol、guard を使用しています:
(defun square-double-digit-p/CLEAN (integer)
(pcase (* integer integer)
((and n (guard (< 9 n 100))) (list 'yes n))
(sorry (list 'no sorry))))
(square-double-digit-p/CLEAN 9) ⇒ (yes 81)
(square-double-digit-p/CLEAN 3) ⇒ (no 9)
(defun square-double-digit-p/MAYBE (integer)
(pcase (* integer integer)
((and n (guard (< 9 (incf n) 100))) (list 'yes n))
(sorry (list 'no sorry))))
(square-double-digit-p/MAYBE 9) ⇒ (yes 81)
(square-double-digit-p/MAYBE 3) ⇒ (yes 9) ; WRONG!
違いは guard の boolean-expression にあります。CLEAN は
n を単純かつ直接的に参照しますが、MAYBE
は式 (incf n) において副作用を伴って n を参照します。integer が 3
のとき、次のようなことが起こります:
n は、それを expval、すなわち
(* 3 3) を評価した結果である 9 にバインドします。
start: (< 9 (incf n) 100) becomes: (< 9 (setq n (1+ n)) 100) becomes: (< 9 (setq n (1+ 9)) 100)
becomes: (< 9 (setq n 10) 100)
; side-effect here!
becomes: (< 9 n 100) ; n now bound to 10
becomes: (< 9 10 100)
becomes: t
nilであるため、guardがマッチし、andがマッチし、制御はその節の本体形式へと移ります。
9が2桁の整数であると主張することの数学的な誤りはさておき、MAYBEにはもう一つの問題があります。ボディフォームで再びnを参照していますが、更新後の値—10—が全く見当たりません。一体どうなったのでしょうか?
まとめると、boolean-expression(guard)だけでなく、expr(let)やfunction(predおよびapp)においても、symbolパターンへの副作用を伴う参照は一切避けるのが最善です。
and である場合、この集合は各サブパターンが let-bind
するすべてのシンボルの和集合となります。and
がマッチするためには、すべてのサブパターンがマッチしなければならないため、これは理にかなっています。
seqpat が or
である場合は事情が異なります。or
は、最初にマッチしたサブパターンでマッチし、残りのサブパターンは無視されます。ボディの形式では、どのサブパターンがマッチしたかを判別して異なるセットから選択する方法がないため、各サブパターンで異なるシンボルセットを
let-bind させる意味はありません。例えば、以下は無効です:
(require 'cl-lib)
(pcase (read-number "Enter an integer: ")
((or (and (pred cl-evenp)
e-num) ; bind e-num to expval
o-num) ; bind o-num to expval
(list e-num o-num)))
Enter an integer: 42 error→ Symbol's value as variable is void: o-num
Enter an integer: 149 error→ Symbol's value as variable is void: e-num
ボディフォーム (list e-num o-num)
の評価はエラーとなります。サブパターンを区別するには、すべてのサブパターンで名称は同一ながら値が異なる別の記号を使用できます。上記の例を再構成すると以下の通りです:
(require 'cl-lib) (pcase (read-number "Enter an integer: ") ((and num ; line 1 (or (and (pred cl-evenp) ; line 2 (let spin 'even)) ; line 3 (let spin 'odd))) ; line 4 (list spin num))) ; line 5
Enter an integer: 42 ⇒ (even 42)
Enter an integer: 149 ⇒ (odd 149)
1行目では、expval の束縛を and と
symbol(この場合は num)で「因数分解」しています。2行目では、or
は前と同様に始まりますが、異なるシンボルを束縛する代わりに、let
を2回使用して(3〜4行目)、両方のサブパターンで同じシンボル spin
を束縛しています。spin
の値がサブパターンを区別します。ボディ形式では、両方のシンボルを参照しています(5行目)。