The Common Lisp Cookbook – パターンマッチング

Table of Contents

The Common Lisp Cookbook – パターンマッチング

📢 🎓 ⭐ Learn Common Lisp efficiently in videos, by the Cookbook's main contributor. Learn more.

🖊️ Discover a new Common Lisp and Coalton editor for beginners: mine and a new VSCode extension for Common Lisp: OLIVE.

ANSI Common Lisp 標準には pattern matching の機能は含まれていませんが、この用途のためのライブラリは存在し、Trivia が community standard になりました。

pattern matching の概念の導入については、Trivia’s wiki を参照してください。

Trivia は非常に多くの Lisp object に対して match でき、拡張可能です。

この library は Quicklisp にあります。

(ql:quickload "trivia")

以下の例では、この library を use します。

(use-package :trivia)

好みに応じて、テスト用の package を作成できます。

(defpackage :trivia-tests (:use :cl :trivia))
(in-package :trivia-tests)

よく使う destructuring pattern

単純な値に match する

match を使うと、変数を他の値に対して test できます。

(let ((x 3))
  (match x
     (0 :no-match)
     (1 :still-no-match)
     (3 :yes)
     (_ :other)))
;; => :yes

list でも同じです。

(let ((x (list :a)))
  (match x
    (0 :nope)
    (1 :still-nope)
    (3 :yes)
    ((list :a) :a-list) ;; <-- match (list :a) entirely. Next we'll match on the content.
    (_ :other)))
;; => :A-LIST

string でも動きます(CL 組み込みの case ではできません)。

(let ((x "a string"))
  (match x
    ("a string" :a-string)
    (_ :other)))
;; => :A-STRING

この知識を使って、洗練された再帰的 Fibonacci 関数を書けます。

(defun fib (n)
  (match n
    (0 0)
    (1 1)
    (_ (+ (fib (- n 1)) (fib (- n 2))))))

変数 x が複合データ構造である場合、その内容に対して match できるとより面白くなります。次の節では Trivia の pattern を使ってそれを行います。

fall-through は _

fall-through case に、condcase で使われる t ではなく _(underscore)を使ったことに注目してください。

cons

cons pattern は list やその他の cons cell に match します。

match される object と pattern の長さは異なっていてもかまいません。下では、yx に match されなかった残りすべてを受け取ります。

(match '(1 2 3)
  ((cons x y)
  ; ^^ pattern
   (print x)
   (print y)))
;; |-> 1
;; |-> (2 3)

list, list*

list は厳密な pattern で、match される object の長さが subpattern の長さと同じであることを期待します。

(match '(something 2 3)
  ((list a b _)
   (values a b)))
SOMETHING
2

_ placeholder がなければ match しません。

(match '(something 2 3)
  ((list a b)
   (values a b)))
NIL

list* pattern は object の長さに関して柔軟です。

(match '(something 2 3)
  ((list* a b)
   (values a b)))
SOMETHING
(2 3)
(match '(1 2 . 3)
  ((list* _ _ x)
   x))
3

ただし、list* が object を1つだけ受け取る場合、その object が list であるかどうかに関係なく、その object が返されることに注意してください。

(match #(0 1 2)
  ((list* a)
   a))
#(0 1 2)

これは HyperSpec における list* の定義に関係しています: http://clhs.lisp.se/Body/f_list_.htm.

vector, vector*

vector は object が vector であるか、長さが同じか、そして内容が各 subpattern に match するかを確認します。

vector* も似ていますが、subpattern の長さ以上であることを許す soft-match variant と呼ばれます。

(match #(1 2 3)
  ((vector _ x _)
   x))
;; -> 2
(match #(1 2 3 4)
  ((vector _ x _)
   x))
;; -> NIL : does not match
(match #(1 2 3 4)
  ((vector* _ x _)
   x))
;; -> 2 : soft match.
<vector-pattern> : vector      | simple-vector
                   bit-vector  | simple-bit-vector
                   string      | simple-string
                   base-string | simple-base-string | sequence
(<vector-pattern> &rest subpatterns)

class と structure の pattern

等価な3つの style があります。

(defstruct foo bar baz)
(defvar *x* (make-foo :bar 0 :baz 1)

(match *x*
  ;; make-instance style
  ((foo :bar a :baz b)
   (values a b))
  ;; with-slots style
  ((foo (bar a) (baz b))
   (values a b))
  ;; slot name style
  ((foo bar baz)
   (values bar baz)))

type, satisfies

type pattern は object がその type であれば match します。satisfies は predicate が object に対して true を返す場合に match します。lambda form も受け付けます。

assoc, property, alist, plist

これらの pattern はすべて、まず pattern が list かどうかを確認します。それが満たされると内容を取得し、値を subpattern に対して match します。assocproperty pattern は単一の値に match します。alistplist pattern は実質的に複数の pattern を and します。

(match '(:a 1 :b 2)
  ((property :a 1) 'found))
;; -> FOUND
(match '(:a 1 :b 2)
  ((property :a n) n))
;; -> 1
(match '(:a 1 :b 2)
  ((property :d n) n))
;; -> NIL

cl:getf と同様に、property pattern に default value を追加できます。cl:getf と異なり、その default が使われているかどうかを捕捉する flag をさらに追加できます。

(match '(:a 1 :b 2)
  ((property :c c 3) c))
;; -> 3
(match '(:a 1 :b 2)
  ((property :c c 3 foundp) (list c foundp)))
;; -> (3 NIL)

property! pattern は、key が実際に存在する場合にだけ match します。

(match '(:a 1 :b 2)
  ((property :d n) (list n))
  (_ 'fail))
;; -> (NIL)
(match '(:a 1 :b 2)
  ((property! :d n) (list n))
  (_ 'fail))
;; -> FAIL

plist を使うと、複数の property に match できます。

(match '(:a 1 :b 2)
  ((plist :a 1 :b x) x))
;; -> 2

assoc pattern は association list に match します。cl:assoc と同じように :test keyword を取れます。

(match '((a . 1) (b . 2) (c . 3))
  ((assoc 'a 1) 'ok))
;; -> OK
(match '((a . 1) (b . 2) (c . 3))
  ((assoc 'b x) x))
;; -> 2
(match '(("one" . 1) ("two" . 2))
  ((assoc "one" x :test #'string-equal) x))
;; -> 1

alist pattern は association list の複数の要素に match します。

(match '((a . 1) (b . 2) (c . 3))
  ((alist ('a . 1) ('c . n)) n))
;; -> 3

Array, simple-array, row-major-array pattern

https://github.com/guicho271828/trivia/wiki/Type-Based-Destructuring-Patterns#array-simple-array-row-major-array-pattern を参照してください。

論理に基づく pattern

任意の pattern をいくらかの logic と組み合わせられます。

and, or

次のものは:

(match x
  ((or (list 1 a)
       (cons a 3))
   a))

(1 2)(4 . 3) の両方に match し、それぞれ 2 と 4 を返します。

not

subpattern が match するときは match しません。subpattern で使われた変数は body から見えません。

guard

guard を使うと、pattern を使い、さらに predicate に対して検証できます。

構文は guard + subpattern + a test form、そして body です。

(match (list 2 5)
  ((guard (list x y)     ; subpattern1
          (= 10 (* x y))) ; test-form
   :ok))

subpattern が真なら test form が評価され、それも真なら subpattern1 に match します。

pattern の入れ子

pattern は入れ子にできます。

(match '(:a (3 4) 5)
  ((list :a (list _ c) _)
   c))

これは 4 を返します。

さらに見る

special patterns を参照してください: place, bind, access

Page source: ja/pattern_matching.md

T
O
C