Ashok Khanna

The With Macro

The WITH macro is used to streamline the use of LET forms in the instances where we desire local variables to have the same names as the function calls whose return values they are bound to. Below is an example of what we mean.

(let ((long-name-of-something (long-name-of-something arg1 arg2 ...))
      (another-long-name (another-long-name arg1 arg2 ...)))
  (some-function long-name-of-something another-long-name))

I personally find the above style of naming variables and function with the same name (a noun) idiomatic of Common Lisp, exemplified by the below example that is commonly encountered:

(let ((list (list arg1 arg2 arg3 ...)))
  (loop for item in list
       collect item))

The WITH macro aims to streamline such LET forms. The benefits are twofold: less repetition of names and a significant reduction in the length of expressions, especially when we use longer and more descriptive names for variables and functions.

The WITH macro can be illustrated with the following reconfiguration of our first example above:

(with ((long-name-of-something arg1 arg2 ...)
       (another-long-name arg1 arg2 ...))
   (some-function long-name-of-something another-long-name))

We can write the WITH macro as follows:

(defmacro with (bindings &body body)
  `(let ,(make-bindforms bindings)
     ,@body))

(defun make-bindforms (bindforms)
  "Transform a list of function calls (fn args) to a list of (fn (fn args))."
  (mapcar #'(lambda (b)
              (list (car b) b))
        bindforms))

A full example:

(defun make-bindforms (bindforms)
  "Transform a list of function calls (fn args) to a list of (fn (fn args))."
    (mapcar #'(lambda (b)
                (list (car b) b))
          bindforms))

(defmacro with (bindings &body body)
  "Equivalent to a let form, with the function name in the binding calls used also as the variable name."
    `(let ,(make-bindforms bindings)
       ,@body))


(defun sum (a b)
  (+ a b))

(defun prod (a b)
  (* a b))


(with ((sum 3 5)
       (prod 4 5))
  (+ sum prod))


Finally, we can similarly define a WITH* macro as follows:

(defmacro with* (bindings &body body)
    `(let* ,(make-bindforms bindings)
       ,@body))

Last updated: 6 September 2021