GNU Emacs 32.0.50 (9008bf64c3) のマニュアルを PLaMo 翻訳で機械翻訳したものです。誤訳を含むことがあります。原文: https://www.gnu.org/software/emacs/manual/html_node/elisp/pcase-Macro.html ・ emacs-doc.conao3.com

11.4.1 pcase マクロ

背景については、パターンマッチング条件式を参照してください。

マクロ: pcase expression &rest clauses

clausesの各節は、(pattern body-forms…)という形式である。

expressionを評価してその値expvalを求める。clausesの中で、patternがexpvalと一致する最初の節を探し、その節のbody-formsに制御を移す。

マッチした場合、pcase の値は、成功節における body-forms の最後の値となります。そうでない場合、pcase は nil と評価されます。

各 pattern は pcase パターン である必要があり、以下で定義されているコアパターンのいずれか、または pcase-defmacro 経由で定義されているパターンのいずれかを使用できます(pcase の拡張 を参照)。

本小節の残りの部分では、コアパターンのさまざまな形式について説明し、いくつかの例を提示した上で、一部のパターン形式で提供されるlet束縛機能を利用する際の重要な注意点を述べて締めくくります。コアパターンには、以下のような形式があります。

_ (underscore)

任意の expval に一致します。これは ドントケア または ワイルドカード とも呼ばれます。

'val

expval が val と等しい場合に一致します。比較は equal によって行われるものとみなされます(等価述語を参照)。

keyword
integer
string

expval がリテラルオブジェクトと等しい場合にマッチします。これは前述の '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))

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 の注意点

前述の例はいずれも、何らかの形でsymbolサブパターンを含むシーケンスパターンを使用しています。その用法に関する重要な詳細を以下に示します。

  1. seqpat 内で symbol が複数回出現する場合、2回目以降の出現は再バインドに展開されず、代わりに 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))
    
  2. symbol を参照する副作用のあるコードは未定義です。避けてください。例えば、以下に類似した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 にバインドします。
    • boolean-expression が評価されます:
      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パターンへの副作用を伴う参照は一切避けるのが最善です。

  3. マッチ時、節の本体はパターンが let-bind するシンボルの集合を参照できます。seqpat が 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行目)。