pcase and the unpopular backquote pattern

classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

pcase and the unpopular backquote pattern

Michael Heerdegen
Hello,

I'm thinking about whether we should install something like this:


From eb8d1f86c744c119ede54cde07a36c1da061c766 Mon Sep 17 00:00:00 2001
From: Michael Heerdegen <[hidden email]>
Date: Wed, 20 Mar 2019 21:21:03 +0100
Subject: [PATCH] * lisp/emacs-lisp/pcase.el: Add patterns list and list*

---
 lisp/emacs-lisp/pcase.el | 26 +++++++++++++++++++++++---
 1 file changed, 23 insertions(+), 3 deletions(-)

diff --git a/lisp/emacs-lisp/pcase.el b/lisp/emacs-lisp/pcase.el
index c0a55f3a41..ae45959ff0 100644
--- a/lisp/emacs-lisp/pcase.el
+++ b/lisp/emacs-lisp/pcase.el
@@ -230,9 +230,10 @@ pcase--make-docstring
                       (when me
                         (push (cons symbol me)
                               more)))))
-        ;; Ensure backquote is first.
-        (let ((x (assq '\` more)))
-          (setq more (cons x (delq x more))))
+        ;; Ensure list stuff is first.
+        (mapc (lambda (m) (let ((x (assq m more)))
+                            (setq more (cons x (delq x more)))))
+              (reverse '(list list* \`)))
         ;; Do the output.
         (while more
           (let* ((pair (pop more))
@@ -1009,5 +1010,24 @@ pcase--u1
    ;; compounded values that are not `consp'
    (t (error "Unknown QPAT: %S" qpat))))

+(pcase-defmacro list (&rest patterns)
+  "Match lists.
+\(list P0 ... Pn-1) matches any list with N elements that are
+matched by patterns P0...Pn-1 in order."
+  `(,'\` ,(mapcar (lambda (pat) `(,'\, ,pat)) patterns)))
+
+(pcase-defmacro list* (&rest patterns)
+  "Match lists with rest.
+\(list* P0 ... Pn-1 R) matches any possibly dotted list with at
+least N elements where the elements with index 0 to N-1 are
+matched by patterns P0...Pn-1 in order, and the list of the
+remaining elements (i.e. the Nth `cdr' of the matched list) is
+matched by pattern R."
+  (unless (cdr patterns)
+    (error "Pattern list* used with less than two arguments"))
+  `(,'\` ,(let* ((l (mapcar (lambda (pat) `(,'\, ,pat)) patterns)))
+            (setcdr (last l 2) (car (last l)))
+            l)))
+
 (provide 'pcase)
 ;;; pcase.el ends here
--
2.20.1



``' is disliked by some people because its semantics are, while elegant
and consistent, not easy to grasp.  I've got the impression that lots
of people dislike pcase mainly for the backquote patterns.

Given that backquote patterns are used mainly for lists, the suggested
list and list* patterns, analogue to functions list and cl-list*, are
not much less powerful, while their syntax and semantics can be
explained without using recursion.  One disadvantage is that when you
want to destructure nested lists you need to use nested "list" patterns.
I guess for some people that would still be easier to read.  Another
disadvantage would be that we would add completely redundant patterns -
but I don't really see a problem here when it would improve readability
for people.  So, like using the backquote macro only for construction of
complicated nested lists, we would use the backquote pcase pattern only
to construct the complicated patterns needing to perform deeper
destructuring.

A second, minor, reason why I find list and list* patterns useful apart
from that is that it helps to avoid the nested backquote mess you get
when you want to define a pcase macro that expands to a backquote
pattern.

If you for example (just for demonstration, I know this could also be
implemented using 'app') you want to define a pattern 'car' that matches
any cons whose car is matched by a pattern you specify, so for example:

(pcase (list "Hallo" 1 2 3)
  ((car (pred stringp)) t))
==> t.

With list* the definition would look like

(pcase-defmacro car (pat)
  `(list* ,pat _))

while without you would need to write it as

(pcase-defmacro car (pat)
  `(,'\` ((,'\, ,pat) . (,'\, _))))

Not something one needs to do often, but when you need to do that, this
,'\, salad is a pain.

list and list* patterns are themselves expanding to backquote patterns
btw, so they do that work for you in the above case.

Ok, if people want something like that, I could replace at least the
simple (one-level) backquote pcase patterns with equivalent list and
list* forms in the Emacs sources.  What do you think?


Michael.


P.S. BTW, if you wonder why we need two patterns list and list*: that's
because an argument list is not allowed to be a real dotted list, like
in (list 1 2 . 3), which is an invalid expression.  For the same reason
the dot syntax can't be used in pattern "list".  Arguments are allowed
to be dotted lists OTOH, so we could alternatively make it so that the
`list' pattern accepts only one argument, a list of patterns, which is
then allowed to be dotted.  That would be something less intuitive OTOH
and I would expect that it should work recursively as well... and then
it's only one more step and we are back to ``'.
Reply | Threaded
Open this post in threaded view
|

Re: pcase and the unpopular backquote pattern

Clément Pit-Claudel
On 2019-03-20 16:57, Michael Heerdegen wrote:
> Given that backquote patterns are used mainly for lists, the suggested
> list and list* patterns, analogue to functions list and cl-list*, are
> not much less powerful, while their syntax and semantics can be
> explained without using recursion.  One disadvantage is that when you
> want to destructure nested lists you need to use nested "list" patterns.
> I guess for some people that would still be easier to read.  Another
> disadvantage would be that we would add completely redundant patterns -
> but I don't really see a problem here when it would improve readability
> for people.  

FWIW, I like the backquote pattern :) I don't think I would use this new form, but I don't think it would hurt either (except maybe when people find out that it breaks in old Emacsen).  Also, it would be one more thing to learn when learning pcase.

> With list* the definition would look like
>
> (pcase-defmacro car (pat)
>   `(list* ,pat _))
>
> while without you would need to write it as
>
> (pcase-defmacro car (pat)
>   `(,'\` ((,'\, ,pat) . (,'\, _))))
>
> Not something one needs to do often, but when you need to do that, this
> ,'\, salad is a pain.

Do you actually need that salad? Isn't the following enough?

(pcase-defmacro car (pat)
  `(\` ((\, ,pat) . (\, _))))


Reply | Threaded
Open this post in threaded view
|

Re: pcase and the unpopular backquote pattern

Michael Heerdegen
Clément Pit-Claudel <[hidden email]> writes:

> FWIW, I like the backquote pattern :) I don't think I would use this
> new form, but I don't think it would hurt either (except maybe when
> people find out that it breaks in old Emacsen).  Also, it would be one
> more thing to learn when learning pcase.

Yes, I like it too.  You are right that it's more thing to learn.
Dunno, maybe it helps people to get in touch with ``' OTOH?  Removing
``' is not an option for me.


> Do you actually need that salad? Isn't the following enough?
>
> (pcase-defmacro car (pat)
>   `(\` ((\, ,pat) . (\, _))))


Hmm, yes, it was a really bad example.  Even this is ok:

(pcase-defmacro car (pat)
  ``(,,pat . ,_))

AFAIR there are cases where you need an unquote-backquote combination,
but it doesn't matter much anyway.


Michael.