HOME

Macros in Emacs Lisp

Compare and symbols

Compare functions in programming languages are commonly three-way, for equality, greater than, and smaller than. The return value is expressed as an int, e.g. -1, 0, 1. But you could also return a symbol:

(require 'cl-lib)

(defun compare (x y)
  (cond
   ((< x y) '<)
   ((> x y) '>)
   (t '=)))

For example:

(dolist (v '((1 2) (2 1) (2 2) (0 10000)))
  (cl-destructuring-bind (x y) v
    (hly/debug-funcall (compare x y))))
(compare 1 2): <
(compare 2 1): >
(compare 2 2): =
(compare 0 10000): <

You could use this in a pattern matcher:

(cl-case (compare 3 54)
  ('< (message "It's smaller"))
  ('> (message "It's larger"))
  ('= (message "It's equal")))
It’s smaller

Macros, Emacs Lisp style

But, really, that just invites its own macro:

(defmacro mcompare (a b &rest forms)
  (org-with-gensyms (res)
    (cl-flet ((quote-first-elem ((sym &rest body)) (cons `',sym body)))
      `(let ((,res (compare ,a ,b)))
         (cl-case ,res
           ,@(mapcar #'quote-first-elem forms)
           (otherwise (error "Unexpected comparison result: %s" ,res)))))))

Yuck!

You can use this macro as follows:

(mcompare 3 54
  (< (message "It's smaller"))
  (> (message "It's larger"))
  (= (message "It's equal")))
It’s smaller

And this throws an error:

(condition-case err
    (mcompare 3 54
      (> (message "It's larger"))
      (= (message "It's equal")))
  (error (message "Got error: %S" err)))
Got error: (error "Unexpected comparison result: <")

Macros, Scheme style

Since we’re on the topic of macros.. those macros look ugly. Let’s try Scheme style macros, aka “MBE”, “Macros By Example”, or “SRFI 46”:

(require 'mbe)

(mbe-defrules hcompare
  ((a b (s body ...) ...)
   (let ((res (compare a b)))
     (cl-case res
       ('s body ...) ...
       (otherwise (error "Unexpected comparison result: %s" res))))))

So clean!

Does it work?

(hcompare 300 54
  (< (message "It's smaller"))
  (> (message "It's larger"))
  (= (message "It's equal")))
It’s larger

Yes.

However…

Hygiene

(let ((res 10))
  (hcompare 300 54
    (< (message "It's smaller, and res is `%s' (should be 10)" res))
    (> (message "It's larger, and res is `%s' (should be 10)" res))
    (= (message "It's equal, and res is `%s' (should be 10)" res))))
It’s larger, and res is ‘>’ (should be 10)

That’s not good: the macro overrides the value of res with its own internal value.

Unfortunately this implementation of SRFI 46 in Emacs Lisp is not hygienic (as it states on the README of that project). Problem is that I’m not sure how to make it so, because the MBE syntax means you can’t easily drop out of the macro…

Org mode help and links

Date: 2021-06-24

Copyright © 2021 Hraban Luyat