Spec-Zone.ru › Elisp

Макрос 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, так что эта привязка доступна в 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. Ниже приведены важные сведения об этом использовании.

  1. Если 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))
    
  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) ; n now 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).

  3. При совпадении формы тела предложения могут обращаться к набору символов, связанных образцом с помощью let. Если seqpat — это and, такой набор является объединением всех символов, связываемых каждым его подобразцом с помощью let. Это логично, поскольку для совпадения and должны совпасть все подобразцы.

    Когда seqpat — это or, ситуация иная: or соответствует при первом совпавшем подобразце, а остальные подобразцы игнорируются. Нет смысла связывать в каждом подобразце свой набор символов, поскольку формы тела не могут определить, какой именно подобразец совпал, и выбрать соответствующий набор. Например, следующий вариант недопустим:

    (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 (строки 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API