The Case of PCase

Recently I have had to take a look into using pcase for pattern matching in elisp. To be quite frank, I had no idea there even was pattern matching support in elisp and always used cond, which is more like a simple switch statement. The more I got into pcase, the more I saw what a powerful tool it was and how much it could simplify a lot of the code I was writing for my emacs packages.

Case Study

Let's assume I have a list of property lists, and I know I should always expect the first plist to contain a property with a certain name, in which case I will execute my logic. This will be our example plist:

  (setq ovs '((:size 6 :color "red") (:name "foo" :prop "bar")))
:size 6 :color red
:name foo :prop bar

Now, let's assume I want to check whether the "size" property is present in the first item, in which case I will print out its value:

    (let ((val (plist-get (car ovs) :size)))
      (if val
          (message "Here is the value %s" val)
        (message "No size property found")))
Here is the value 6

Nice, but we are making some assumptions here, first of all, we assume we always get a plist as an argument. Then we are creating this val variable so we won't repeat the same function calls later for our message.

One way we could check these assumptions is the following, starting from making sure that what we are passing is indeed a list:

  (let ((ovs '((:size 6 :color "red") (:name "foo" :prop "bar"))))
    (pcase ovs
      ((`(,a ,b)
       (message "Size is %s" (plist-get a :size))))
      (_ (message "anything else"))))
Size is 6

So, what did we just do? One cool thing that you can do with pcase is deconstruction, we can actually do 2 things at the same time, first is making sure what is being passed is a list, the second is that it contains two obects, and we can even call the first one without having to use car. However, we could also consider cases where more than 2 items are present in the plist:

  (let ((ovs '((:size 6 :color "red") (:name "foo" :prop "bar"))))
    (pcase ovs
      (`(,a . ,_)
       (message "Size is %s" (plist-get a :size)))
      (_ (message "anything else"))))
Size is 6

In this case we want to match a list that has at least one item, we do not care for the other items, so we bind them to underscore _, like in python for unused variables. However, we may want to explicitly first check that the first item has a :size key, in oder to do that, we can use the guard function inside an and clause, this will ensure that not only what we are passing is a list of 1 or more items, but also that that first item has a :size key:

  (let ((ovs '((:size 6 :color "red") (:name "foo" :prop "bar"))))
    (pcase ovs
      ((and `(,a . ,_)
            (guard (plist-member a :size)))
       (message "Size is %s" (plist-get a :size)))
      (_ (message "anything else"))))
Size is 6

Right now, however, we handle all other cases just the same, but maybe we want to handle cases where the object passed is not a list in a particular way. Let's handle the case of passing an integer instead by adding a new pattern match and using the pred function.

    (let ((ovs 5));'((:size 6 :color "red") (:name "foo" :prop "bar"))))
      (pcase ovs
        ((and `(,a . ,_)
              (guard (plist-member a :size)))
         (message "Size is %s" (plist-get a :size)))
        ((and (pred (integerp)))
         (message "You did not pass a list but an integer!"))
        (_ (message "anything else"))))
You did not pass a list but an integer!

We can also use nested pcases, may seem excessive, but it can turn out handy in some cases. In the following, we use (pred (not function)) to match anything that is not an integer, then we have a nested pcase that will handle integer cases by binding the value of what we passed to the variable somenumber.

      (let ((ovs 3));'((:size 6 :color "red") (:name "foo" :prop "bar"))))
        (pcase ovs
          ((and `(,a . ,_)
                (guard (plist-member a :size)))
           (message "Size is %s" (plist-get a :size)))
          ((and (pred (not integerp)))
           (message "You did not pass a list nor an integer!"))
          ((and (pred (integerp)) somenumber)
           (pcase somenumber
             (5 (message "you passed 5!"))
             (_ (message "You passed an integer that is not 5"))))))
You passed an integer that is not 5

Final comments

I have gone over and, pred and guard as functions that can be used with pcase, but there are also or, let (to bind the value to a variable), cl-type, app and rx (to match using regex). These are explained in the docs ( https://www.gnu.org/software/emacs/manual/html_node/elisp/pcase-Macro.html ).

It was a pretty interesting journey this one, while it can be a little challenging or unusual as an approach to pattern matching, the capabilities and usefulness of pcase in emacs lisp make it a valuable tool that can make code more concise and readable.