The function approach is useful and it solves my problem. I need a dictionary, I get a dictionary. However, what I am doing is essentially akin to binding/destructuring. I only have to rely on the information on the left side of the binding, as the right side completely lacks such information. Moreover, in some situation it is nice to directly access the "fields". I definitely decided that it was time for a macro.
Notice, I'm quite against macro abuse. I've already abused many nice programming features as soon as I learned them. Consequently I know I can resist this time. But this is definitely too nice not to use a macro. My first attempt was this:
It essentially retains the same structure of the function, but is a macro. It is slightly more robust because it makes clear what happens when the array is too short (the variables are nil) or when it is too long (variables are ignored). It stresses that what matters is just the keys parameter, while the defaults only help. In particular it is clear that having keys in the defaults that are not among keys is completely useless. Consequently, order in defaults is plainly irrelevant and plain dictionaries (instead of array-map's) can be used.
However, when I started substituting the code in the project, I felt that the code did not look particularly nicer. I immediately started thinking to a new solution. An afterthought is that my typical use-case (get a dictionary to merge with .state) is not easily covered.
In "Artificial Intelligence Programming: Case Studies in Common Lisp" some suggestions regarding macros are given at the very beginning of the book. Norvig explains that writing a macro is a four-step process:
- Decide if the macro is really necessary.
- Write down the syntax of the macro
- Figure out what the macro should expand to
- Use defmacro to implement the syntax/expansion correspondence
I considered that in order to read the other macro a programmer needed either to look at the implementation or to the documentation. On the other hand, following clojure conventions, the meaning of the code would be apparent to everybody knowing about clojure destructuring.
That is an example on how to use the macro. And now, the macro itself!
Not perfect, but it does its job.