Return a function so you don't repeat yourself
When designing a web client api the functions can end up looking similar. Everything boils down to the request execution. Each api function is just a bit different based on the exact http-method, api end point, or parameters. Removing this duplication might not be straightforward. A recent code review request had this situation. The author ended up writing a macro to extract the repetition.
I thought that by writing [a macro].. I wouldn’t have to repeat myself for every function and instead let the macro do the code generation for me?
This macro uses its parameters in a straightforward manner. It only unquotes (
~) them. No other code runs during the expansion. This is a great example to explore and see how a functional approach can replace it.
Separate code structure work from functional work
To start, it is important to separate the functional work from the syntax changes the macro does. The bulk of the work happens in the
let block. The
let extracts into a function named
This provides us a simpler macro to look at. Now the macro is only adding the syntax requirements for a multi-arity function. You might come to a standstill here, but functions can replace this too!
Function creation is functional
Function creation does not need a macro’s syntax manipulation. Function creation can happen in normal functions. Refactoring and pulling it out of the macro requires multiple steps. First lets separate
defn into its
fn. One obstacle to this seperation is the recursion on
fns in clojure take an optional name argument. This allows recursion by using this internal name. Lets call this one
There is a problem will extracting the
fn directly. It closes over the
~verb and the
~url. Those need to be parameters. But the function stored by
def-spotify-api-call needs to take 1 or 2 parameters. Solving this problem requires adding another
fn. It will take the additional two arguments and return the original
This provides a binding for the inner
fn to close over
url#. Everything in the outer
fn is local to it. Pulling it out allows for a name. Lets call it
spotify-api-call is a function constructor. Given the
url it will return a function. Calling that function later with extra data will do the actual work.
Reviewing the macro
At this point
def-spotify-api-call just shifts tokens around. Compare its macro invocation to the expansion.
The macro no longer reduces code duplication. That was its purpose. Removing it makes sense.
Keeping it dry
The original code had a macro so the author “wouldn’t have to repeat myself”. The macro separated into code structure work vs functional work. A function constructor was able to return a multi-arity function definition. That reduced the macro to just token shifting, so it was removed. The end result keeps things dry with just functions.