use loop

loop is one of the most useful functions one could use for sequences. It can do a bunch of things:

  1. It starts from a number and ends in a number
  2. It can range over a list, vector, hash-table
  3. It can collect or append values through each iteration to put them into a list
  4. It can define variables at the start of a loop

There are videos online that go much deeper on how powerful loops are. But, you want to use them in macros because macros return lists and loops are the perfect tool to return lists and subsequently, code forms. For example, I recently wanted to generate constants for a protocol I am trying to parse.

The protocol’s constants start from a certain number and end with a certain number. But, each constant has two versions a Tconstant and Rconstant. So, effectively if the value of constant was 1, Tconstant would be 1, Rconstant would be 2. Tconstant2 would be 3 and so on.

Here’s how I used loop to define those constants:

(defun rfmt (&rest args)
  (read-from-string (apply #'fmt `(nil ,@args))))

(defparameter *messages-enum*
  (loop
    :for x :in
    `(:openfd :version :auth :attach :error :flush :walk :open :create :read :write :clunk :remove :stat :wstat)
    :for i :from 98 :by 2
    :append (list (rfmt ":t~a" x) i (rfmt ":r~a" x) (1+ i))))

That loop expression generates a list that looks like this:

(:topenfd 98 :ropenfd 99 :tversion 100 :rversion 101)

And so on. Cool, ain’t it?

mapcar

When loop is only used for collecting a function form for a list, use mapcar.

(loop for x in list collect (+ 5 x))
;; use this
(mapcar (lambda (x) (+ 5 x)) list)

Although the difference isn’t drastic, mapcar is better etiqutte. Both produce the same result. I sometimes use loop’s do in-order to set elements in a vector or string like so:

(loop for i from 0 below (length list) do (setf (elt list i) (+ 5 (elt list i))))

Which is quite verbose but it gets the job done across vectors, arrays, strings(which are arrays) as well as lists too.

use tiny functions once the macro gets too big

Macros get bloated way too soon. My way to combat this was to create little functions that return lists that resemble code. For example, if I had a macro that generated a function and I needed to declare the types of my arguments, I’d just define a function like so:

(defun declare-for-my-macro (arg)
  (case arg
    (number 'integer)
    (string 'string)
    (t 't)))

Then use it in a macro like so:

(defun collect-declare-forms (arg)
  `(,(declare-for-my-macro arg) ,arg))

(defmacro my-macro ()
  `(defun some-random-function (string number other)
    (declare ,(mapcar #'collect-declare-forms `(string number other)))
    (print (format nil "~a ~a ~a" string number other))))

Which returns:

(defun some-random-function (string number other)
  (declare ((string string) (integer number) (t other)))
  (print (format nil "~a ~a ~a" string number other)))

macroexpand-1 is there for you

macroexpand-1 singlehandedly carries all the macros in Common Lisp on its back and can carry more. What this neat little function does is it expands the macro without evaluating it.

So, imagine you writing a macro and you are having issues with its expansion. You could always invoke macroexpand-1 to see what the result of your macro is before evaluating it:

;; from the macro defined above
;; the return value is the same as the one above
(macroexpand-1 `(my-macro))
(macroexpand-1 `(loop repeat 5 collect (random 5)))

You can also use macroexpand which expands ALL macros in your macro. For example, if you had a loop within the return value of a macro, that gets evaluated and what is returned is the final result of a macro.