pcaseДля общей информации см. Условное сопоставление с образцом.
Каждая ветвь в ветвях имеет вид:
(образец тело-формы…).
Вычислить выражение, чтобы определить его значение, знач-выр. Найти первую ветвь в ветвях, чей образец сопоставляется с знач-выр, и передать управление тело-формам этой ветви.
Если сопоставление найдено, значением pcase является значение
последней из тело-форм в успешной ветви.
В противном случае pcase вычисляется в nil.
Каждый образец должен быть образцом pcase, который может использовать
либо один из основных образцов, определённых ниже, либо один из образцов,
определённых через pcase-defmacro (см. Расширение pcase).
В остальной части этого подраздела описываются различные формы основных образцов, приводятся некоторые примеры и в заключение рассматриваются важные предостережения относительно использования механизма связывания let, предоставляемого некоторыми формами образцов. Основной образец может иметь следующие формы:
_ (подчёркивание)Сопоставляется с любым знач-выр. Это также известно как всё равно или шаблон.
'значСопоставляется, если знач-выр равно знач. Сравнение выполняется
как с помощью equal (см. Предикаты равенства).
ключевое-словоцелоестрокаСопоставляется, если знач-выр равно литеральному объекту.
Это частный случай 'знач, описанного выше,
возможный, поскольку литеральные объекты этих типов являются самоцитируемыми.
символСопоставляется с любым знач-выр и дополнительно связывает символ с знач-выр с помощью let, так что эта привязка доступна в тело-формах (см. Динамическая привязка).
Если символ является частью последовательного образца посл-обр
(например, с использованием and ниже), привязка также доступна в
той части посл-обр, которая следует за появлением символа.
Это использование имеет некоторые предостережения, см. предостережения.
Двух символов следует избегать: t, который ведёт себя как _
(выше) и устарел, и nil, который вызывает ошибку.
Аналогично, не имеет смысла связывать символы-ключевые слова
(см. Переменные, которые никогда не изменяются).
`кв-обрОбразец в стиле обратного апострофа. Подробности см. Образцы в стиле обратного апострофа.
(cl-type тип)Сопоставляется, если знач-выр имеет тип тип, который является дескриптором
типа, принимаемым cl-typep (см. Предикаты типов в Расширениях Common Lisp). Примеры:
(cl-type integer) (cl-type (integer 0 10))
(pred функция)Сопоставляется, если предикат функция возвращает не-nil
при вызове с знач-выр. Проверка может быть инвертирована с помощью синтаксиса
(pred (not функция)).
Предикат функция может иметь одну из следующих форм:
Вызвать именованную функцию с одним аргументом, знач-выр.
Пример: integerp
Вызвать анонимную функцию с одним аргументом, знач-выр (см. Лямбда-выражения).
Пример: (lambda (n) (= 42 n))
Вызвать функцию (первый элемент вызова функции) с n аргументами (остальные элементы) и дополнительным n+1-м аргументом, которым является знач-выр.
Пример: (= 42)
В этом примере функцией является =, n равно единице, и
фактический вызов функции становится: (= 42 знач-выр).
_Вызвать функцию (первый элемент вызова функции)
с указанными аргументами (остальные элементы), заменяя
_ на знач-выр.
Пример: (gethash _ memo-table)
В этом примере функцией является gethash, и
фактический вызов функции становится: (gethash знач-выр memo-table).
(app функция образец)Сопоставляется, если функция, вызванная с знач-выр, возвращает
значение, которое сопоставляется с образец.
функция может принимать одну из форм, описанных для pred
выше. Однако, в отличие от pred, app проверяет результат
на соответствие образец, а не на булево значение истинности.
(guard булево-выражение)Сопоставляется, если булево-выражение вычисляется в не-nil.
(let образец выр)Вычисляет выр для получения знач-выр2 и сопоставляется, если знач-выр2
сопоставляется с образец. (Названо let, потому что образец
может связывать символы со значениями с помощью символ.)
Последовательный образец (также известный как посл-обр) — это
образец, который обрабатывает свои под-образцы-аргументы последовательно.
Их два для pcase: and и or.
Они ведут себя аналогично специальным формам,
разделяющим их имена (см. Конструкции для комбинирования условий),
но вместо обработки значений они обрабатывают под-образцы.
(and образец1…)Пытается сопоставить образец1… по порядку, пока один из них
не сопоставится неудачно. В этом случае and также не сопоставляется,
и остальные под-образцы не проверяются. Если все под-образцы
сопоставляются, and сопоставляется.
(or образец1 образец2…)Пытается сопоставить образец1, образец2, … по порядку,
пока один из них не завершится успешно. В этом случае or также сопоставляется,
и остальные под-образцы не проверяются.
Чтобы предоставить согласованное окружение
(см. Введение в вычисление)
для тело-форм (тем самым избегая ошибки вычисления при сопоставлении),
набор переменных, связываемых образцом, является объединением
переменных, связываемых каждым под-образцом. Если переменная не связана
под-образцом, который сопоставился, то она связывается с nil.
(rx rx-выр…)Сопоставляет строки с регулярным выражением rx-выр…, используя
нотацию регулярных выражений rx (см. Структурированная нотация регулярных выражений rx), как если бы
с помощью string-match.
В дополнение к обычному синтаксису rx, rx-выр… может
содержать следующие конструкции:
(let ссылка rx-выр…)Связать символ ссылка с подстрокой, которая сопоставляется
с rx-выр.... ссылка связывается в тело-формах со
строкой подстроки или nil, но также может использоваться в
backref.
(backref ссылка)Как стандартная конструкция backref, но ссылка здесь также
может быть именем, введённым предыдущей конструкцией (let ссылка …).
cl-caseВот пример, который подчёркивает некоторые преимущества pcase
перед cl-case
(см. Условные конструкции в Расширениях Common Lisp).
(pcase (get-return-code x) ;; строка ((and (pred stringp) msg) (message "%s" msg))
;; символ
('success (message "Готово!"))
('would-block (message "Извините, сейчас не могу"))
('read-only (message "Шмильблик доступен только для чтения"))
('access-denied (message "У вас нет необходимых прав"))
;; по умолчанию (code (message "Неизвестный код возврата %S" code)))
С cl-case вам пришлось бы явно объявить локальную
переменную code для хранения возвращаемого значения get-return-code.
Кроме того, cl-case сложно использовать со строками, потому что он
использует eql для сравнения.
andРаспространённый приём — записывать образец, начинающийся с and,
с одним или несколькими под-образцами символ, предоставляющими привязки
для последующих под-образцов (а также для тело-форм).
Например, следующий образец сопоставляется с однозначными целыми числами.
(and
(pred integerp)
n ; связать n с знач-выр
(guard (<= -9 n 9)))
Сначала pred сопоставляется, если (integerp знач-выр)
вычисляется в не-nil.
Затем n — это образец символ, который сопоставляется
с чем угодно и связывает n с знач-выр.
Наконец, 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)
Переформулировка демонстрирует связывание символ, а также
or, and, pred, app и let.
(defun grok/pcase (obj)
(pcase obj
((or ; строка 1
(and ; строка 2
(pred stringp) ; строка 3
(pred (string-match ; строка 4
"^key:\\([[:digit:]]+\\)$")) ; строка 5
(app (match-string 1) ; строка 6
val)) ; строка 7
(let val (list "149" 'default))) ; строка 8
val))) ; строка 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 знач-выр)
возвращает nil, pred не сопоставляется, и, следовательно, and также не сопоставляется.
Следующий pred (строки 4–5) вычисляет
(string-match RX знач-выр)
и сопоставляется, если результат не равен nil, что означает,
что знач-выр имеет нужную форму: key:NUMBER.
Опять же, если это не так, pred не сопоставляется и and — тоже.
Наконец (в этой серии под-образцов and), app
вычисляет (match-string 1 знач-выр) (строка 6)
для получения временного значения врем (т.е. подстроки «NUMBER»)
и пытается сопоставить врем с образцом val (строка 7).
Поскольку это образец символ, он сопоставляется безусловно
и дополнительно связывает val с врем.
Теперь, когда app сопоставился, все под-образцы and
сопоставились, и, значит, and сопоставляется.
Аналогично, как только and сопоставился, or сопоставляется
и не переходит к попытке под-образца let (строка 8).
Рассмотрим ситуацию, когда obj не является строкой
или является строкой, но имеет неправильную форму.
В этом случае один из pred (строки 3–5) не сопоставляется,
следовательно, and (строка 2) не сопоставляется,
следовательно, or (строка 1) переходит к попытке под-образца let (строка 8).
Сначала let вычисляет (list "149" 'default)
для получения ("149" default), знач-выр2, а затем
пытается сопоставить знач-выр2 с образцом val.
Поскольку это образец символ, он сопоставляется безусловно
и дополнительно связывает val с знач-выр2.
Теперь, когда let сопоставился, or сопоставляется.
Обратите внимание, как под-образцы and и let завершаются
одинаково: попыткой (всегда успешной) сопоставить образец
символ val, связывая при этом val.
Таким образом, or всегда сопоставляется, и управление всегда передаётся
тело-форме (строка 9).
Поскольку это последняя тело-форма в успешно сопоставленной
ветви pcase, она является значением pcase и, соответственно,
возвращаемым значением grok/pcase (см. Что такое функция?).
Предыдущие примеры используют последовательные образцы, которые так или иначе включают под-образец символ. Вот некоторые важные подробности об этом использовании.
eq.
В следующем примере представлена форма pcase
с двумя ветвями и двумя посл-обр, A и B.
И A, и B сначала проверяют, что знач-выр является
парой (с использованием pred),
а затем связывают символы с car и cdr
знач-выр (используя по одному app).
Для A, поскольку символ st упоминается дважды, второе
упоминание становится проверкой на равенство с помощью eq.
С другой стороны, B использует два разных символа, s1 и
s2, оба из которых становятся независимыми привязками.
(defun grok (object)
(pcase object
((and (pred consp) ; посл-обр A
(app car st) ; первое упоминание: st
(app cdr st)) ; второе упоминание: st
(list 'eq st))
((and (pred consp) ; посл-обр B
(app car s1) ; первое упоминание: s1
(app cdr s2)) ; первое упоминание: 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))
and, символ и 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) ; НЕВЕРНО!
Разница в булево-выражении в guard:
CLEAN ссылается на n просто и напрямую,
а MAYBE ссылается на n с побочным эффектом
в выражении (incf n).
Когда integer равно 3, происходит следующее:
n связывает его с знач-выр,
т.е. с результатом вычисления (* 3 3), то есть 9.
начало: (< 9 (incf n) 100) становится: (< 9 (setq n (1+ n)) 100) становится: (< 9 (setq n (1+ 9)) 100)
становится: (< 9 (setq n 10) 100)
; побочный эффект здесь!
становится: (< 9 n 100) ; n теперь связано с 10
становится: (< 9 10 100)
становится: t
nil,
guard сопоставляется, and сопоставляется, и
управление передаётся тело-формам этой ветви.
Помимо математической некорректности утверждения, что 9 является
двузначным целым, есть ещё одна проблема с MAYBE.
Тело-форма ссылается на n ещё раз, но мы не видим
обновлённое значение — 10 — вообще. Что с ним случилось?
Резюмируя, лучше вообще избегать ссылок с побочными эффектами на
образцы символ, не только
в булево-выражении (в guard),
но также в выр (в let)
и функция (в pred и app).
and, этот набор является
объединением всех символов, которые связывает каждый из его под-образцов.
Это имеет смысл, потому что для сопоставления and
все под-образцы должны сопоставиться.
Когда посл-обр — это or, ситуация иная:
or сопоставляется с первым под-образцом, который сопоставился;
остальные под-образцы игнорируются.
Не имеет смысла, чтобы каждый под-образец связывал с помощью let разные
наборы символов, потому что тело-формы не имеют возможности различить,
какой под-образец сопоставился, и выбрать один из разных наборов.
Например, следующий код недопустим:
(require 'cl-lib)
(pcase (read-number "Введите целое число: ")
((or (and (pred cl-evenp)
e-num) ; связать e-num с знач-выр
o-num) ; связать o-num с знач-выр
(list e-num o-num)))
Введите целое число: 42 ошибка→ Значение символа как переменной: void: o-num
Введите целое число: 149 ошибка→ Значение символа как переменной: void: e-num
Вычисление тело-формы (list e-num o-num) вызывает ошибку.
Чтобы различать под-образцы, можно использовать другой символ,
одинаковый по имени во всех под-образцах, но различающийся по значению.
Перерабатывая приведённый выше пример:
(require 'cl-lib) (pcase (read-number "Введите целое число: ") ((and num ; строка 1 (or (and (pred cl-evenp) ; строка 2 (let spin 'even)) ; строка 3 (let spin 'odd))) ; строка 4 (list spin num))) ; строка 5
Введите целое число: 42 ⇒ (even 42)
Введите целое число: 149 ⇒ (odd 149)
Строка 1 «выносит» связывание знач-выр с помощью
and и символ (в данном случае num).
В строке 2 or начинается так же, как и раньше,
но вместо связывания разных символов дважды использует let
(строки 3–4) для связывания одного и того же символа spin в обоих под-образцах.
Значение spin различает под-образцы.
Тело-форма ссылается на оба символа (строка 5).