Pattern Matching: pcase

Haskell allows pattern matching. The following function counts one, two or many objects

simpleCount 1 = "One"
simpleCount 2 = "Two"
simpleCount _ = "Many"

You can use pattern matching to set base cases in recursive functions.

factorial 0 = 1
factorial n = n * factorial (n-1)

Haskell also allows guards. This if statement checks if someone is old enough to drive in the UK

canDrive x = if x<18 then  "Too young to drive" else "Old enough to drive"

Here it is using guards:

canDrive x
          | x<18 = "Too young to drive"       |
          | otherwise = "Old enough to drive" |

pcase

Emacs Lisp offers similar functionality with the pcase macro. It took me some time to understand the documentation, so here are few examples to get you going. They only scratch the surface, make sure you go back and read up properly afterwards.

(defun simple-count (x)
  (pcase x
    (1 "one")
    (2 "two")
    (_ "many")))

(mapcar #'simple-count '(1 2 5))
=> ("one" "two" "many")

Note that _ is used for the don’t care or wildcard case, rather than the more traditional t.

(defun can-drive (x)
  (pcase x
    ((guard (< x 18)) "Too young to drive")
    (_ "Old enough to drive")))

(can-drive 12)
=> "Too young to drive"

The following converts a test mark into a grade. Note the use of and to evaluate (pred stringp). If non nil, it binds x to msg. In other words, pcase can distinguish between marks and teacher comments.

(defun student-grade (x)
  (pcase x
    ((and (pred stringp) msg) msg)
    ((guard (< x 10)) "Fail")
    ((guard (< x 20)) "C")
    ((guard (< x 30)) "B")
    (_ "A")))

(mapcar #'student-grade '("Absent" 23 12 "off roll" 9 35))
=> ("Absent" "B" "C" "off roll" "Fail" "A")

Take a look at this example from the documentation. Again, it uses and to evaluate (pred stringp). If non nil, it binds x to msg.

So, if x is a string, print it; if x is a recognised symbol, print the associated message; otherwise print unknown return code.

(defun my-errors (x)
  (pcase 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))))

(mapcar #'my-errors '(1 read-only "hello"))
=> ("Unknown return code 1" "The shmliblick is read-only" "hello")

4 Comments

  1. Phil says:

    I think you may have used org-babel to do this, and copied its output table formatting verbatim into this article, which is confusing.

    (mapcar #’simple-count ‘(1 2 5))
    => | one | two | many |

    Should be:

    (mapcar #’simple-count ‘(1 2 5))
    => (“one” “two” “many”)

    Like

    1. Tony says:

      Thanks, Phil! I’ll change it

      Like

  2. Ivanjoy says:

    How’s that msg came from?

    Like

    1. Tony says:

      Take a look at the documentation: https://www.gnu.org/software/emacs/manual/html_node/elisp/pcase-Macro.html. expval is bound to msg if it’s a string

      Like

Leave a Comment