Макрос 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(см. Предикаты равенства). keywordintegerstring-
Соответствует, если expval равен литеральному объекту. Это частный случай
'valвыше, возможный потому, что литеральные объекты этих типов цитируются автоматически. symbol-
Соответствует любому expval и дополнительно связывает symbol с expval с помощью let, так что эта привязка доступна в body-forms (см. Динамическая привязка).
Если symbol входит в шаблон последовательности seqpat (например, при использовании
andниже), привязка также доступна в части seqpat, следующей за появлением symbol. У такого использования есть некоторые ограничения; см. предостережения.Следует избегать двух символов:
t, который ведёт себя как_(выше) и считается устаревшим, иnil, который вызывает ошибку. Аналогично, связывать ключевые слова бессмысленно (см. Переменные, которые никогда не изменяются). `qpat-
Образец в стиле обратной кавычки. Подробности см. в разделе Образцы в стиле обратной кавычки.
(cl-type type)-
Соответствует, если expval имеет тип type — дескриптор типа, принимаемый
cl-typep(см. Предикаты типов в Расширениях Common Lisp). Примеры:(cl-type integer) (cl-type (integer 0 10))
(pred function)-
Соответствует, если предикат function возвращает значение, отличное от
nil, при вызове с аргументом expval. Проверку можно инвертировать синтаксисом(pred (not function)). Предикат function может иметь одну из следующих форм:- имя функции (символ)
-
Вызвать указанную функцию с одним аргументом — expval.
Пример:
integerp - лямбда-выражение
-
Вызвать анонимную функцию с одним аргументом — expval (см. Лямбда-выражения).
Пример:
(lambda (n) (= 42 n)) - вызов функции с n аргументами
-
Вызвать функцию (первый элемент вызова функции) с n аргументами (остальными элементами) и дополнительным аргументом с номером n+1, равным expval.
Пример:
(= 42)В этом примере функция —=, n равно единице, а фактический вызов функции выглядит так:(= 42 expval). - вызов функции с аргументом
_ -
Вызвать функцию (первый элемент вызова функции) с указанными аргументами (остальными элементами), заменив
_на expval.Пример:
(gethash _ memo-table)В этом примере функция —gethash, а фактический вызов функции выглядит так:(gethash expval memo-table).
(app function pattern)-
Соответствует, если вызов function с аргументом expval возвращает значение, соответствующее pattern. function может иметь одну из форм, описанных выше для
pred. Однако, в отличие отpred,appпроверяет результат на соответствие pattern, а не на истинность логического значения. (guard boolean-expression)-
Соответствует, если boolean-expression вычисляется в значение, отличное от
nil. (let pattern expr)Вычисляет expr, получая exprval, и соответствует, если exprval соответствует pattern. (Образец называется
let, потому что pattern может связывать символы со значениями с помощью symbol.)
Шаблон последовательности (также известный как seqpat) — это образец, который обрабатывает аргументы подобразцов по порядку. Для pcase существуют два таких шаблона: and и or. Они ведут себя подобно одноимённым специальным формам (см. Конструкции для объединения условий), но обрабатывают не значения, а подобразцы.
(and pattern1…)-
Пытается сопоставить pattern1… по порядку, пока один из них не окажется несовпадающим. В этом случае
andтакже не соответствует, а остальные подобразцы не проверяются. Если совпали все подобразцы,andсоответствует. (or pattern1 pattern2…)-
Пытается сопоставить pattern1, pattern2, … по порядку, пока один из них не совпадёт. В этом случае
orтакже соответствует, а остальные подобразцы не проверяются.Чтобы обеспечить согласованное окружение (см. Введение в вычисление) для body-forms и тем самым избежать ошибки вычисления при совпадении, набор переменных, связываемых образцом, является объединением переменных, связываемых каждым подобразцом. Если переменная не связана совпавшим подобразцом, ей присваивается
nil. (rx rx-expr…)-
Сопоставляет строки с регулярным выражением rx-expr…, используя нотацию регулярных выражений
rx(см. Структурированная нотация регулярных выраженийrx), как если бы использовалсяstring-match.Помимо обычного синтаксиса
rx, rx-expr… может содержать следующие конструкции:(let ref rx-expr…)-
Связать символ ref с подсовпадением, соответствующим rx-expr.... В body-forms символ ref связывается со строкой подсовпадения или с
nil, но его также можно использовать вbackref. (backref ref)Подобно стандартной конструкции
backref, но здесь ref также может быть именем, введённым предыдущей конструкцией(let ref …).
Пример: преимущества перед cl-case
Ниже приведён пример, иллюстрирующий некоторые преимущества pcase перед cl-case (см. Условные конструкции в Расширениях Common Lisp).
(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 вам пришлось бы явно объявить локальную переменную code, чтобы сохранить возвращаемое значение get-return-code. Кроме того, cl-case неудобно использовать со строками, поскольку для сравнения применяется eql.
Пример: использование and
Распространённый приём — начинать образец с and, а затем добавлять один или несколько подобразцов symbol, связывающих переменные с последующими подобразцами (а также с формами тела). Например, следующий образец соответствует целым числам, состоящим из одной цифры.
(and
(pred integerp)
n ; bind n to expval
(guard (<= -9 n 9)))
Сначала pred соответствует, если (integerp expval) вычисляется в значение, отличное от nil. Далее, n — это образец symbol, который соответствует чему угодно и связывает n с expval. Наконец, guard соответствует, если логическое выражение (<= -9 n 9) (обратите внимание на ссылку на n) вычисляется в значение, отличное от nil. Если совпали все эти подобразцы, соответствует and.
Пример: переписывание с помощью pcase
Вот ещё один пример, показывающий, как переписать простую задачу сопоставления с традиционной реализации (функция grok/traditional) на реализацию с использованием pcase (функция grok/pcase). Строка документации обеих функций: «Если OBJ — строка вида "key:NUMBER", вернуть NUMBER (строку). В противном случае вернуть список ("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 ; 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. Ниже приведены важные сведения об этом использовании.
- Если symbol встречается в seqpat более одного раза, второе и последующие вхождения не разворачиваются в повторную привязку, а превращаются в проверку равенства с использованием
eq.В следующем примере показана форма
pcaseс двумя предложениями и двумя seqpat, A и B. Оба образца сначала проверяют, что expval является парой (с помощьюpred), а затем связывают символы сcarиcdrзначения expval (используя по одномуappдля каждого).В A символ
stупоминается дважды, поэтому второе упоминание превращается в проверку равенства с использованиемeq. В B, напротив, используются два отдельных символа —s1иs2, каждый из которых становится независимой привязкой.(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))
- Код с побочными эффектами, обращающийся к symbol, не определён. Избегайте его. Например, ниже приведены две похожие функции. Обе используют
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!Различие заключается в boolean-expression внутри
guard:CLEANобращается кnнапрямую, тогда какMAYBEобращается кnс побочным эффектом в выражении(incf 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) ;nnow bound to 10 becomes: (< 9 10 100) becomes: t - Поскольку результат вычисления отличен от
nil, совпадаетguard, совпадаетand, и управление передаётся формам тела этого предложения.
Помимо математической некорректности утверждения, что 9 — целое число из двух цифр, с
MAYBEсвязана ещё одна проблема. Форма тела ещё раз обращается кn, но мы совсем не видим обновлённого значения — 10. Куда оно делось?Итак, лучше вообще избегать обращений с побочными эффектами к образцам symbol — не только в boolean-expression (в
guard), но и в expr (вlet) и function (вpredиapp). - Первый
- При совпадении формы тела предложения могут обращаться к набору символов, связанных образцом с помощью let. Если seqpat — это
and, такой набор является объединением всех символов, связываемых каждым его подобразцом с помощью let. Это логично, поскольку для совпаденияandдолжны совпасть все подобразцы.Когда seqpat — это
or, ситуация иная:orсоответствует при первом совпавшем подобразце, а остальные подобразцы игнорируются. Нет смысла связывать в каждом подобразце свой набор символов, поскольку формы тела не могут определить, какой именно подобразец совпал, и выбрать соответствующий набор. Например, следующий вариант недопустим:(require 'cl-lib) (pcase (read-number "Enter an integer: ") ((or (and (pred cl-evenp) e-num) ; binde-numto expval o-num) ; bindo-numto 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). В строке 2orначинается так же, как и раньше, но вместо связывания разных символов дважды используетlet(строки 3–4), чтобы связать один и тот же символspinв обоих подобразцах. Значениеspinпозволяет различать подобразцы. В форме тела используются оба символа (строка 5).
Copyright © 1990-1996, 1998-2026 Free Software Foundation, Inc.
Licensed under the GNU GPL license.
https://www.gnu.org/software/emacs/manual/html_node/elisp/pcase-Macro.html