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)としても知られる。
'val
expvalとvalが等しければマッチ。比較はequal
のように行われる(同等性のための述語を参照)。
keyword
integer
string
expvalがリテラルオブジェクトと等しければマッチ。これは上述の'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)
になる。
_
引数での関数呼び出し関数(関数呼び出しの1つ目の要素)を指定された引数(残りの要素)で呼び出し、expvalを_
で置き換える。
例: (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)
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)。