3

Consider the following form:

(pcase '(:def "k" #'foo :wk "ho")
  (`(:def
     ,(and (or (pred stringp) (pred vectorp)) key)
     ,(and (pred xl-sharp-quoted-symbol-p) def)
     :wk
     ,(and (pred stringp) desc))
   (format "%s | %s | %s" key def desc)))
;; =>
"k | #'foo | ho"

Suppose that I want the keywords :def and :wk to be optional. In other words I want the pcase form to successfully parse the following lists:

'("k" #'foo "bar")
'("k" #'foo :wk "bar")
'(:def "k" #'foo "bar")

Of course I realize it is possible to copy over the clause in my first example in all possible combinations omitting either :def, :wk or both. However, that's tedious and duplicates much code.

I wish I could do something like this:

(pcase '(:def "k" #'foo :wk "ho")
  (`(,(optional :def)
     ,(and (or (pred stringp) (pred vectorp)) key)
     ,(and (pred xl-sharp-quoted-symbol-p) def)
     ,(optional :wk)
     ,(and (pred stringp) desc))
   (format "%s | %s | %s" key def desc)))

Is there a way to do this concisely with pcase? If so, how?

1

The backquote pcase pattern `--pcase-macroexpander is implemented using pcase-defmacro in the library pcase.el .

There is no pattern included there that does what you discussed. But, it may be added as shown in the following Elisp code that defines an alternative pcase pattern bq-opt.

The added lines are cleanly added as one block. This block is framed by two lines only consisting of semi-colons.

(pcase-defmacro bq-opt (qpat)
  "Pattern (bq-opt QPAT) dealing with ?\\, like the backquote pattern.
QPAT can take the following forms:
  ((optional QPAT1) . QPAT2)  matches if QPAT1 matches the car
                            and QPAT2 matches the cdr or if QPAT2 matches
  (QPAT1 . QPAT2)           matches if QPAT1 matches the car and QPAT2 the cdr.
  ,PAT                      matches if the `pcase' pattern PAT matches.
  SYMBOL                    matches if EXPVAL is `equal' to SYMBOL.
  KEYWORD                   likewise for KEYWORD.
  NUMBER                    likewise for NUMBER.
  STRING                    likewise for STRING.

The list or vector QPAT is a template.  The predicate formed
by a backquote-style pattern is a combination of those
formed by any sub-patterns, wrapped in a top-level condition:
EXPVAL must be \"congruent\" with the template.  For example:

  (bq-opt technical ,forum)

The predicate is the logical-AND of:
 - Is EXPVAL a list of two elements?
 - Is the first element the symbol `technical'?
 - True!  (The second element can be anything, and for the sake
   of the body forms, its value is bound to the symbol `forum'.)"
  (declare (debug (pcase-QPAT)))
  (cond
   ((eq (car-safe qpat) '\,) (cadr qpat))
   ((vectorp qpat)
    `(and (pred vectorp)
          (app length ,(length qpat))
          ,@(let ((upats nil))
              (dotimes (i (length qpat))
                (push `(app (pcase--flip aref ,i) ,(list '\` (aref qpat i)))
                      upats))
              (nreverse upats))))
   ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
   ;;; Added case (optional QPAT)
   ((pcase qpat
      (`((optional ,qpat1) . ,qpat2)
       `(or (and (pred consp)
               (app car ,qpat1)
               (app cdr ,(list 'bq-opt qpat2)))
            ,(list 'bq-opt qpat2)))))
   ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
   ((consp qpat)
    `(and (pred consp)
          (app car ,(list 'bq-opt (car qpat)))
          (app cdr ,(list 'bq-opt (cdr qpat)))))
   ((or (stringp qpat) (numberp qpat) (symbolp qpat)) `',qpat)
   ;; In all other cases just raise an error so we can't break
   ;; backward compatibility when adding \` support for other
   ;; compounded values that are not `consp'
   (t (error "Unknown QPAT: %S" qpat))))

Usage example with optional keywords in EXPVAL:

(pcase '(:def "k" foo :wk "ho")
  ((bq-opt ((optional :def)
            ,(and (or (pred stringp) (pred vectorp)) key)
            ,(and (pred symbolp) def)
            (optional :wk)
            ,(and (pred stringp) desc)
            ))
   (format "%s | %s | %s" key def desc)))

Result:

"k | foo | ho"

Usage example without optional keywords in EXPVAL:

(pcase '("k" foo "ho")
  ((bq-opt ((optional :def)
            ,(and (or (pred stringp) (pred vectorp)) key)
            ,(and (pred symbolp) def)
            (optional :wk)
            ,(and (pred stringp) desc)
            ))
   (format "%s | %s | %s" key def desc)))

Result:

"k | foo | ho"
4
  • Could you explain a bit the logic behind the code block you added for the solution? I'm having a difficult time understanding it, specifically the (or ..) form. Also, I would rather add this clause to ``--pcase-macroexpander` itself because I may want to extend the macro in the future and I don't want to create a different alternative pattern each time--I'd rather extend the existing one. I think I'd need make the clause you added not use pcase in that case, right? – Aquaactress Feb 17 at 20:11
  • Also, this does not work for vectors. – Aquaactress Feb 21 at 22:33
  • @Aquaactress Sorry, I will answer in more detail when I have some spare time. (I am really busy right now.) I thought the source code would be quite self-explanatory so I didn't go too deep into the details. As time allows I will add some more comments. The vector case does not work since this was solely an answer to the question with a proof-of-concept and not a productive solution. I have also another simple idea that should cover all cases. Maybe, I add another answer as time allows. – Tobias Feb 22 at 6:49
  • I highly recommend not to touch such basic stuff like pcase with advice-add. Rather write your own version (bound to a different symbol), especially if it is as simple as in this case. Make a feature-request at bug-gnu-emacs@gnu.org if you think that your feature is general enough to be included into pcase. – Tobias Feb 22 at 6:55
0

My second solution is not an attempt to modify or replace the backquote pattern of pcase
but it introduces a new pcase macro that works in collaboration with backquote patterns.

It does quite exactly what you proposed in your question:

Of course I realize it is possible to copy over the clause in my first example in all possible combinations omitting either :def, :wk or both. However, that's tedious and duplicates much code.

But, the tedious duplication of code is done by the pcase macro optional.
The doc-string of optional already describes quite good how it works.

(pcase-defmacro optional (pat-opt pat-tail)
  "Define a pattern (optional PAT-OPT PAT-TAIL) for `pcase'.
The pattern matches
- if EXPVAL is a cons with the car matching PAT-OPT
  and the cdr matching PAT-TAIL
  -- or --
- if EXPVAL matches PAT-TAIL."
  (declare (debug (pcase-PAT pcase-PAT)))
  `(or (and (app car ,pat-opt) (app cdr ,pat-tail))
       ,pat-tail))

As already mentioned, optional is a pattern keyword that you can use in conjunction with backquoted patterns.
It is not parsed by a modified backquote-pcase-macro as in my first solution.

Therefore, the pattern for your usage example looks a bit different from your proposed pattern.
Nevertheless, you do not need to write the tail behind the optional keyword twice.

(pcase val
    ((optional
      :def
      `(,(and (or (pred stringp) (pred vectorp)) key)
        ,(and (pred symbolp) def)
        .
        ,(optional
          :wk
          `(,(and (pred stringp) desc)))))
     (format "%s | %s | %s" key def desc)))

The evaluation of this example gives:

"k | foo | ho"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.