Macros Design Patterns
I was reading the thread What are some fun or useful macros? on reddit and it reminded me of another thread that appeared in the Pro mailing list. These kind of threads are always enjoyable because each time you learn something new and see really interesting things. While reading them, it crossed my mind that another variant of this question would be what are some fun or useful macros design patterns. Instead of examples of specific code macros (general or not) it would be nice to see common programming practices using macros. So, for a lack of a better expression name, let’s call them macros design patterns.
My favorite one is to configure an algorithm, especially when we want to use the correct types. Essentially, we write an algorithm using a macro that takes types or configuration arguments and then we expand it to the appropriate desired configurations. For example, if you have an algorithm that operates on different types of sequences, instead of writing several duplicate functions with the same algorithm but with the associated type declarations, just apply this pattern. A simple but contrived example: we want to compute the mean of a vector but use its proper type. In addition, we might also want the possibility of using a key to access the vector elements. We can write the following macro:
(defmacro mean-body (vector vector-type vector-ref key) (let ((size (gensym)) (i (gensym))) `(locally (declare (type ,vector-type ,vector)) (let ((,size (length ,vector))) (/ (loop for ,i from 0 below ,size sum ,(if key `(funcall ,key (,vector-ref ,vector ,i)) `(,vector-ref ,vector ,i))) ,size)))))
The macro contains the algorithm (in this simple case the mean) and the arguments allow us to configure the multiple versions we need. If we want a simple-vector, the macro will expand to use the correct type declaration and svref. If a key function is needed it will also include it. Then, we can call the macro with the several configurations value inside the main function:
(defun mean (vector &optional key) (typecase vector (simple-vector (if key (mean-body vector simple-vector svref key) (mean-body vector simple-vector svref nil))) (vector (if key (mean-body vector vector aref key) (mean-body vector vector aref nil))) (otherwise (if key (mean-body vector sequence elt key) (mean-body vector sequence elt nil)))))
This can be very useful in situations where we want to optimize code since it becomes easy to add the proper type declarations to the input arguments of an algorithm. Moreover, we keep the algorithm in a single place, making it easier to maintain. Depending on the situation, we can also define a function for each configuration. In the example we could have a mean-simple-vector and mean-vector.
I don't know if it has already a specific name but I like to call it the configurable algorithm pattern. I find it very useful. And thinking back to the reddit thread, what are your favorite macros design patterns? Which ones do you find useful and use them regularly? If you want to share, feel free to drop a line. I am interested in seing and learning other patterns!