This post makes use of places. If you are unfamiliar with how places work, see my post Getting Places.
Many languages provide syntactic sugar for evaluating an expression involving a variable and assigning the result of that expression to the variable at the same time. In these languages you can do something such as the following:
x += 5
The above expression both adds five to the value of x and writes that new value back to x. In this post, I’m going to show you how you can write a macro zap that is a generalized version of this technique. With zap the above example would look like the following:
(zap #'+ x 5)
There are a couple things that make zap really cool. First of all, it can be used with any function. For example, if you wanted to cube the value in x, you could use the following:
(zap #'expt x 3)
The other thing that makes zap so awesome is that it can be used on any place. If you want to use zap on the value stored in a hash table with key 5, you can do that:
(zap #'+ (gethash 5 table) 5)
Now that you’ve seen how zap is used, here is how it can be implemented:
(defmacro zap (fn place &rest args) (multiple-value-bind (temps exprs stores store-expr access-expr) (get-setf-expansion place) `(let* (,@(mapcar #'list temps exprs) (,(car stores) (funcall ,fn ,access-expr ,@args))) ,store-expr)))
You should be able to see that the code for zap is eerily similar to that of incf (from Getting Places). They are the exact same except instead of binding the gensym that will hold the new value to one plus the value already in the place:
(,(car stores) (+ 1 ,access-expr))
The gensym is bound to the result of calling the function with the value in the place and all of the other arguments passed to zap:
(,(car stores) (funcall ,fn ,access-expr ,@args))
Although zap is just a nice syntactic shortcut, it is a great example of the crazy things you can do with places.