Delving Into Macros
Posted by postfuturist on 2010-11-03 22:41:50

I go through periods of obsession with different programming languages. I don't know why, I guess I get bored easily. I usually get stuck using the same 2 or 3 at my day job. Since I work in web development, that tends to be PHP, Python and JavaScript. When I hack for fun, I'm not very goal-oriented, and I tend to want to explore and try new and exciting things. Well, new for me at least. Lately, I've been on a Common Lisp kick again. I've been writing code. I got to a point where I wanted to be able to pop the "nth" element from a list. Strangely, this doesn't seem to exist in Common Lisp. In Python, the "pop" method on lists has an optional parameter for which element to remove and return:

x = [1,2,3,4]
y = x.pop(1)
print(x) # [1,3,4]
print(y) # 2

So I had to write one, which involved learning how macros work again. Here's my first attempt:

(defmacro popnth (n lst)
  (let ((tempvar (gensym)))
    `(if (eql ,n 0)
      (pop ,lst)
      (let ((,tempvar (nth ,n ,lst)))
        (setf (cdr (nthcdr ,(- n 1) ,lst)) (nthcdr ,(+ n 1) ,lst))
        ,tempvar))))

Unfortunately, that makes 1 call to "nth" and 2 calls to "nthcdr" making the whole thing probably a bit slower than necessary for larger lists. So here is a version that only iterates through the list once:

(defmacro popnth (n lst)
  (let ((t1 (gensym))(t2 (gensym)))
    `(if (eql ,n 0)
      (pop ,lst)
      (let* ((,t1 (nthcdr ,(- n 1) ,lst))
              (,t2 (car (cdr ,t1))))
        (setf (cdr ,t1) (cddr ,t1))
        ,t2))))

Of course, there's a subtle error in these examples, the unquoting is off in the subtraction. Here it is fixed:

(defmacro popnth (n lst)
  (let ((t1 (gensym))(t2 (gensym)))
    `(if (eql ,n 0)
      (pop ,lst)
      (let* ((,t1 (nthcdr (- ,n 1) ,lst))
              (,t2 (car (cdr ,t1))))
        (setf (cdr ,t1) (cddr ,t1))
        ,t2))))

blog comments powered by Disqus