Next: pcaseの拡張, Up: パターンマッチングによる条件 [Contents][Index]
pcaseマクロ背景はパターンマッチングによる条件を参照してください。
clauses内のclauseは(pattern body-forms…)という形式をもつ。
expressionの値( expval )を決定するために評価する。patternがexpvalにマッチするような最初のclauseを探して、そのclauseのbody-formsに制御を渡す。
マッチが存在すればpcaseの値はマッチが成功したclauseのbody-formsの最後の値、それ以外ならpcaseはnilに評価される。
各patternはpcaseパターン(pcase
pattern)である必要があります。これは以下に定義されるコアパターンのいずれか、またはpcase-defmacro
(pcaseの拡張を参照)を通じて定義されるパターンの1つを使用できます。
このサブセクションの残りの部分ではコアパターンの異なるフォームをいくつかの例を交えて説明して、いくつかのパターンフォームが提供するletバイディング機能を使用する上で重要な注意点で締めくくります。コアパターンは以下のフォームをもつことができます:
_ (アンダースコア)任意のexpvalにマッチ。これはdon’t care、あるいはワイルドカード(wildcard)としても知られる。
'valexpvalとvalが等しければマッチ。比較はequalのように行われる(同等性のための述語を参照)。
keywordintegerstringexpvalがリテラルオブジェクトと等しければマッチ。これは上述の'valの特殊なケースであり、これらのタイプのリテラルオブジェクトが自己クォート(self-quoting)を行うために可能となる。
symbol任意のexpvalにマッチするとともに、追加でsymbolをexpvalにletバインドする。このようなバインディングはbody-forms内でも利用できる(ダイナミックバインディングを参照)。
symbolが(以下のandを使用することにより)シーケンスパターンseqpatの一部なら、symbolに後続するseqpat部分でもバインディングは利用可能。この使用法にはいくつかの注意点がある。caveatsを参照のこと。
使用を避けるべき2つのシンボルは_
(上述)と同様に振る舞う非推奨のtとnil(エラーをシグナルする)。同様にキーワードシンボルへのバインドは無意味(変更不可な変数を参照)。
`qpatバッククォートスタイルのパターン。詳細についてはバッククォートスタイルパターンを参照のこと。
(cl-type type)expvalがタイプtype ( cl-typepが許す型記述子。 Type
Predicates in Common Lisp Extensionsを参照)ならマッチ。たとえば:
(cl-type integer) (cl-type (integer 0 10))
(pred function)expvalにたいして述語functionが非nilをリターンしたらマッチ。このテストは構文(pred
(not function))で否定となる。述語functionは以下のフォームのいずれかが可能:
expvalを単一の引数として名前つきの関数を呼び出す。
例: integerp
expvalを単一の引数として無名関数を呼び出す(ラムダ式を参照)。
例: (lambda (n) (= 42 n))
関数(関数呼び出しの1つ目の要素)をn個の引数(残りの要素)、およびexpvalをn+1番目の追加の引数としてで呼び出す。
例: (= 42)
この例では関数が=、nが1であり、実際の関数呼び出しは(= 42 expval)になる。
(app function pattern)expvalにたいして呼び出したfunctionがpatternにマッチする値をリターンすればマッチ。functionは上述のpredで説明したフォームのいずれかが可能。しかしpredとは異なり、appはブーリーンの真値ではなくpatternにたいして結果をテストする点が異なる。
(guard boolean-expression)boolean-expressionが非nilに評価されればマッチ。
(let pattern expr)exprvalを取得するためにexprを評価して、exprvalがpatternにマッチすればマッチ(patternはsymbolを使用することでシンボルに値をバインドできるのでletと呼ばれる)。
seqpatとしても知られるシーケンスパターン(sequencing
pattern)はサブパターンを順に処理するパターンです。pcaseにたいしてはandとorの2つが存在します。これらは同じ名前のスペシャルフォーム(組み合わせ条件の構築を参照)と同様の方式で振る舞いますが、値ではなくサブパターンを処理します。
(and pattern1…)pattern1…のいずれかがマッチに失敗するまで順にマッチを試みる。この場合にはandもマッチに失敗して、残りのサブパターンはテストしない。すべてのサブパターンがマッチすればandはマッチする。
(or pattern1 pattern2…)pattern1、pattern2、…のいずれかがマッチに成功するまで順にマッチを試みる。この場合にはorもマッチして、残りのサブパターンはテストしない。
body-formsにたいして一貫した環境
(評価の概要を参照)
を与える(マッチでの評価エラーを回避できる)ために、パターンがバインドする変数セットは各サブパターンがバインドする変数を結合したものになる。ある変数がマッチしたサブパターンにバインドされない場合にはnilにバインドされる。
(rx rx-expr…)string-matchが行うようにrx正規表現表記を使用して文字列を正規表現rx-expr…にマッチする(rx構造化Rgexp表記を参照)。
通常のrx構文に加えて、rx-expr…には以下の構文を含めることができる:
(let ref rx-expr…)シンボルrefをrx-expr...にマッチする部分マッチにバインドする。refはbody-forms内で部分マッチの文字列かnilにバインドされるが、backrefでも使用可能。
(backref ref)標準のbackref構文と同様だが、refは前の(let ref
…)構文で導入された名前にもなる。
cl-caseにたいする利点以下はcl-case (Conditionals in Common Lisp
Extensionsを参照)にたいするpcaseの利点のいくつかを強調する例です。
(pcase (get-return-code x) ;; 文字列 ((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 "未知のリターンコード %S" code)))
cl-caseではget-return-codeのリターン値を保持するためにローカル変数codeを宣言する必要があります。さらにcl-caseは比較にeqlを使用するので文字列の使用も難しくなります。
andの使用後続のサブパターン(と同様にbodyフォーム)にバインディングを提供する1つ以上のsymbolサブパターンをもつandで開始するのが、パターンを記述する際の一般的なイディオムです。たとえば以下のパターンは1桁の整数にマッチします。
(and
(pred integerp)
n ; nにexpvalをバインド
(guard (<= -9 n 9)))
まずpredは(integerp expval)が非nilに評価されればマッチになります。次にnはすべてにマッチするsymbolパターンであり、nにexpvalがバインドされます。最後にguardはブーリーン式(<= -9 n 9)が非nilに評価されればマッチになります(nへの参照に注意)。これらすべてのサブパターンがマッチすればandがマッチになります。
pcaseによる書き換え以下はシンプルなマッチングタスクを伝統的な実装(関数grok/traditionalから、pcaseを使用する実装(関数grok/pcase)に書き換える別の例です。これらの関数のdocstringはいずれも“If
OBJ is a string of the form "key:NUMBER", return NUMBER (a
string). Otherwise, return the list ("149"
default).”です。最初は伝統的な実装です(正規表現を参照):
(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 ; L1
(and ; L2
(pred stringp) ; L3
(pred (string-match ; L4
"^key:\\([[:digit:]]+\\)$")) ; L5
(app (match-string 1) ; L6
val)) ; L7
(let val (list "149" 'default))) ; L8
val))) ; L9
(grok/pcase "key:0") ⇒ "0"
(grok/pcase "key:149") ⇒ "149"
(grok/pcase 'monolith) ⇒ ("149" default)
grok/pcaseは主に1つのpcaseフォームのclause、L1からL8のパターン、L9の(1つの)フォームからなります。パターンは引数であるサブパターンにたいして順にマッチを試みるorです。これは最初にand
(L2からL7)、次にlet (L8)のように、いずれかが成功するまで順にマッチを試みます。
前出の例(Example
1を参照)のように、andは以降のサブパターンが正しいタイプのオブジェクト(この場合は文字列)に作用することを保証するためにpredサブパターンで始まります。(stringp expval)がnilならpredは失敗となり、したがってandも失敗となります。
次のpred (L4からL5)は(string-match RX expval)を評価して結果が非nil (
expvalが期待するフォームkey:NUMBERであることを意味する)ならマッチになります。これが失敗すると再びpredは失敗となり、したがってandも失敗となります。
(andの一連のサブパターンでは)最後のappは一時的な値tmp (部分文字列
“NUMBER”)を取得するために(match-string 1 expval)
(L6)を評価して、パターンval
(L7)とtmpのマッチを試みます。これはsymbolパターンなので無条件でマッチして、valにtmpを追加でバインドします。
ついにappがマッチしたので、andのすべてのサブパターンがマッチして、andがマッチとなります。同じように一度andがマッチすればorがマッチするので、サブパターンlet
(L8)の処理が試みられることはありません。
objが文字列以外、あるいは間違った形式の文字列の場合を考えてみましょう。この場合にはいずれかのpred
(L3からL5)がマッチに失敗するので、and (L2)がマッチに失敗して、or (L1)がサブパターンlet
(L8)の処理を試みます。
まずletは("149" default)を取得するために(list "149" 'default)を評価して、それからパターンvalにたいしてexprvalのマッチを試みます。これはsymbolパターンなので無条件にマッチして、追加でvalにexprvalをバインドします。これでletがマッチしたので、orがマッチとなります。
andとletのサブパターンはどちらも同じ方法、すなわちvalをバインドする過程でsymbolパターンのvalに(常に成功する)マッチを試みることにより完了することに注意してください。したがってorは常にマッチして、常に制御をbodyフォーム(L9)に渡します。マッチが成功したpcaseのclauseとしては最後のbodyなので、これはpcaseの値となり、同様にgrok/pcaseのリターン値になります(関数とは?を参照)。
前出の例のすべてでは、何らかの方法によりsymbolサブパターンが含まれるシーケンスパターンが使用されています。以下に使用法に関する重要な詳細をいくつか挙げます。
eqを使用した等価性テストに展開される。
以下の例には2つのclauseと2つのseqpat
(AとB)を使用している。AとBはいずれも最初に(predを使用することにより)expvalがペアであることをチェックして、それから(それぞれにたいしてappを使用することにより)
expvalのcarとcdrにシンボルをバインドする。
Aではシンボルstが2回記述されているので、2つ目の記述はeqを使用した等価性チェックになる。一方でBはs1とs2という別個のシンボルを使用するので、独立したバインディングになる。
(defun grok (object)
(pcase object
((and (pred consp) ; seqpat A
(app car st) ; st: 1回目
(app cdr st)) ; st: 2回目
(list 'eq st))
((and (pred consp) ; seqpat B
(app car s1) ; s1: 1回目
(app cdr s2)) ; s2: 1回目
(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の際には以下のようなことが発生している:
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)
; ここで副作用!
becomes: (< 9 n 100) ; nは10にバインドされている
becomes: (< 9 10 100)
becomes: t
nilなので
guardがマッチしてandがマッチとなり、制御はそのclauseのbodyフォームに渡される。
MAYBEには9が2桁の整数だと判定してしまう数学的な誤り以外にも問題がある。bodyフォームはnの更新された値(10)を確認せずに参照を複数回行う。するとどうなるか?
要約すると( guardでの) boolean-expressionだけではなく( letでの)
expr、( predとappでの)
functionでも副作用をもつsymbolパターンにたいする参照は完全に避けることが最良である。
andの際には、各サブパターンでletバインドされるすべてのシンボルそれぞれを結合したものになる。andのマッチではすべてのサブパターンがマッチしなければならないので、これには意味がある。
seqpatがorなら事情は異なる。orは最初にマッチしたサブパターンでマッチとなり、残りのサブパターンは無視される。bodyフォームにはどのサブパターンがマッチして異なるセットの中からどれが選択されたかを知る術はないので、各シンボルが異なるシンボルセットをletバインドすることに意味はない。たとえば以下は無効:
(require 'cl-lib)
(pcase (read-number "Enter an integer: ")
((or (and (pred cl-evenp)
e-num) ; e-numをexpvalにバインド
o-num) ; o-numを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
bodyフォーム(list e-num o-num)の評価によりエラーがシグナルされる。サブパターンを区別するために、すべてのサブパターンごとに異なる値をもつ同一名のシンボルを使用できる。上記の例を書き換えると:
(require 'cl-lib) (pcase (read-number "Enter an integer: ") ((and num ; L1 (or (and (pred cl-evenp) ; L2 (let spin 'even)) ; L3 (let spin 'odd))) ; L4 (list spin num))) ; L5
Enter an integer: 42 ⇒ (even 42)
Enter an integer: 149 ⇒ (odd 149)
L1ではexpvalのバインディング(この場合はnum
)をandとsymbolで“分解”している。L2では前と同じ方法でorは始まるが、異なるシンボルにバインドするかわりに、両方のサブパターン内で同一のシンボルspinに回バインドするために2回letを使用している(L3からL4)。spinの値によりサブパターンは区別される。そしてbodyフォームでは両方のシンボルを参照している(L5)。
Next: pcaseの拡張, Up: パターンマッチングによる条件 [Contents][Index]