Constants In Lisp Macro Calls

I ran into a frustrating programming problem today, and one that I’d really like to find a solution to.

I had an array of a fixed size, and I wanted to move all the items within that array down by one and add a new item at the beginning of it. In other words, if I had an array of size 3, I wanted to move item 1 to item 2, item 0 to item 1, and put a new item in item 0. I came up with some code that used a loop to move the items, but I wanted to write a macro to use the shiftf function with it instead. The array had a fixed size, and that size was defined by a constant. The code looked something like this:

(defmacro shift-array-down (arr arr-length newitem)
  `(shiftf ,@(loop for i from (1- arr-length) downto 0
                   collecting `(svref ,arr ,i))
           ,newitem))

(Again, my apologies for the formatting; the spaces are there, but WordPress doesn’t show them. Any decent Lisp editor will reformat the lines properly.)

This worked, and worked quite well… so long as I used an actual number for arr-length. But the array was defined with a constant for the length, which I’ll call +array-length+, and I wanted to use that instead of a hard-coded number. SBCL wouldn’t let me — it claimed that +array-length+ wasn’t a number, despite the fact that it was defined as the same constant integer that I used for my test calls to shift-array-down.

I think I can see the technical reason for it: the macro call isn’t translating the constant to it’s value before using it, and it isn’t part of the macro environment. But does this mean that there’s no way to use constants in macro calls? I’m certain it doesn’t, that would be an unacceptable limitation to Lisp developers, so I’m sure there’s an answer. But after spending over an hour looking for it, I decided to abandon the search and get back to the original problem instead (I had a deadline). Does anyone know what it is?

I’ll keep searching myself, of course, but if anyone has an answer, I’d appreciate learning it.

6 Comments

  1. It seems that I have no problem if I use the constant within the macro itself, but I’m not permitted to use it as a parameter to the macro. Which, again, doesn’t make sense — in this case, it means that I’d have to make a different macro for each different size of array I want to use this with. I’ll keep looking for an answer.

  2. You’re right, the macro isn’t translating the constant to its value before using it. As an example of this:

    (defconstant +shift-array-size+ 10)

    (defmacro constant-macro (var) (format t “VAR is ~a~%” var))

    (defun constant-macro-test () (constant-macro +shift-array-size+))

    When compiled, the following is printed:

    VAR is +SHIFT-ARRAY-SIZE+

    So, to get it to use the constant, you have to force it to evaluate the constant string inside the macro by using (eval). For example:

    (defconstant +shift-array-size+ 10)

    (defmacro constant-macro (var) (format t “VAR is ~a~%” (eval var)))

    (defun constant-macro-test () (constant-macro +shift-array-size+))

    When compiled, the following is printed:

    VAR is 10

    So, to have your shift-array-down function work like you want it you, you just need to have it evaluate the arr-length parameter:

    (defmacro shift-array-down (arr arr-length newitem) (shiftf ,@(loop for i from (1- (eval arr-length)) downto 0 collecting(svref ,arr ,i)) ,newitem))

    That will work both when the arr-length is a number and when it’s a reference to a constant.

  3. Nice — it does the trick. 🙂 Thanks, that’ll make things a little easier.

  4. (newbie lisper hat on)

    (eval) is pretty nifty in general, an interpreter within an interpreter, if I remember correctly.

  5. Nifty, yes, but everything I’ve read suggests that you avoid using it. Although this would be a special case where it’s okay to do so, because Lisp macros are processed at compile-time anyway.

  6. I’ve discovered that the proper answer to this is to use SYMBOL-VALUE instead of EVAL. As described on this page:

    Novices almost always misuse EVAL. When experts use EVAL, they often would be better off using APPLY, FUNCALL, or SYMBOL-VALUE. Use of EVAL when defining a macro should set off a warning bell — macro definitions are already evaluated during expansion. See also the answer to question 3-12. The general rule of thumb about EVAL is: if you think you need to use EVAL, you’re probably wrong.

    I’ve confirmed that SYMBOL-VALUE will do the trick for this too.

Comments are closed.