Spec-Zone.ru › Elisp

Макрос pcase

Для справки, см. Сопоставление с образцом — Условные выражения.

Макрос: pcase выражение &rest клаузы

Каждая клауза в clauses имеет вид: (pattern body-forms…).

Вычислите выражение, чтобы определить его значение, expval. Найдите первую клаузу в clauses, шаблон которой соответствует expval, и передайте управление телу клаузы body-forms.

Если есть соответствие, значение pcase — это значение последнего из body-forms в успешной клаузе. В противном случае, значение pcase равно nil.

Каждый шаблон должен быть шаблоном pcase, который может использовать любой из основных шаблонов, определённых ниже, или один из шаблонов, определённых с помощью pcase-defmacro (см. Расширение pcase).

Остальная часть этого подраздела описывает различные формы основных шаблонов, приводит примеры и завершается важными замечаниями об использовании механизма связывания let, предоставляемого некоторыми формами шаблонов. Основной шаблон может иметь следующие формы:

_ (underscore)

Соответствует любому значению expval. Это также известно как любой или подстановочный знак.

'val

Соответствует, если expval равно val. Сравнение выполняется так, как если бы оно выполнялось с помощью equal (см. Предикаты равенства).

keyword
integer
string

Соответствует, если expval равен литеральному объекту. Это частный случай 'val, выше, возможный потому, что литеральные объекты этих типов самоцитируются.

symbol

Соответствует любому значению expval, и дополнительно связывает символ со значением expval, так что это связывание доступно для body-forms (см. Динамическое связывание).

Если символ является частью шаблона последовательности seqpat (например, с использованием and, ниже), связывание также доступно для части seqpat после появления символ. Это использование имеет некоторые нюансы, см. замечания.

Следует избегать двух символов: t, который ведет себя как _ (выше) и устарел, и nil, который сигнализирует об ошибке. Аналогично, не имеет смысла связывать символы ключевых слов (см. Постоянные переменные).

(cl-type type)

Соответствует, если expval имеет тип type, который является описателем типа, принятым cl-typep (см. Предикаты типа в Расширения Common Lisp). Примеры:

(cl-type integer)
(cl-type (integer 0 10))
(pred function)

Соответствует, если предикат функция возвращает ненулевое значение при вызове с expval. Тест может быть инвертирован с помощью синтаксиса (pred (not function)). Предикат функция может иметь одну из следующих форм:

имя функции (символ)

Вызовите функцию с одним аргументом expval.

Пример: integerp

лямбда-выражение

Вызовите анонимную функцию с одним аргументом expval (см. Лямбда-выражения).

Пример: (lambda (n) (= 42 n))

вызов функции с n аргументами

Вызовите функцию (первый элемент вызова функции) с n аргументами (остальные элементы) и дополнительным n+1-м аргументом, который равен expval.

Пример: (= 42) В этом примере функция равна =, n равно одному, а фактический вызов функции становится: (= 42 expval).

(app function pattern)

Соответствует, если функция функция, вызванная с expval, возвращает значение, которое соответствует шаблону. функция может принимать одну из форм, описанных для pred, выше. Однако в отличие от pred, app проверяет результат на соответствие шаблону, а не на логическое истинное значение.

(guard boolean-expression)

Соответствует, если логическое_выражение вычисляется до ненулевого значения.

(let pattern expr)

Вычисляет expr, чтобы получить exprval, и соответствует, если exprval соответствует шаблону. (Это называется let, потому что шаблон может связывать символы со значениями с помощью символ).

Шаблон последовательности (также известный как seqpat) — это шаблон, который последовательно обрабатывает свои подшаблоны. Существуют два шаблона последовательности: 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.... ref связывается в body-forms со строкой подстроки или 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 shmliblick 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, с одним или несколькими подшаблонами символ, предоставляющими связи с последующими подшаблонами (а также с формами тела). Например, следующий шаблон соответствует однозначным целым числам.

(and
  (pred integerp)
  n                     ; bind n to expval
  (guard (<= -9 n 9)))

Сначала pred соответствует, если (integerp expval) вычисляется до ненулевого значения. Далее n — это шаблон символ, который соответствует любому значению и связывает n со значением expval. Наконец, guard соответствует, если логическое выражение (<= -9 n 9) (обратите внимание на ссылку на n) вычисляется до ненулевого значения. Если все эти подшаблоны соответствуют, and соответствует.

Пример: Переформулировка с pcase

Вот ещё один пример, который показывает, как переформулировать простую задачу сопоставления из её традиционной реализации (функция grok/traditional) в реализацию с использованием pcase (функция grok/pcase). Документация для обеих этих функций: «Если OBJ — это строка вида "ключ:ЧИСЛО", верните ЧИСЛО (строку). В противном случае верните список ("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)

Переформулировка демонстрирует связывание символ, а также 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) и соответствует, если результат ненулевой, что означает, что expval имеет нужную форму: key:NUMBER. Опять же, если это не так, pred терпит неудачу, а также и and.

Наконец (в этом ряду подшаблонов and), app вычисляет (match-string 1 expval) (строка 6), чтобы получить временное значение tmp (т.е. подстроку “ЧИСЛО”) и пытается сопоставить tmp с шаблоном val (строка 7). Поскольку это шаблон символ, он соответствует безусловно и дополнительно связывает 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. Как 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. В формах тела пункта при сопоставлении можно ссылаться на набор символов, который паттерн связывает. Когда seqpat имеет вид and, этот набор является объединением всех символов, которые связывает каждый из подпаттернов. Это имеет смысл, потому что для того, чтобы 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-2022 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