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 exec-request!
.
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 def
and fn
. One obstacle to this seperation is the recursion on ~f
. Luckily fn
s in clojure take an optional name argument. This allows recursion by using this internal name. Lets call this one g#
.
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 fn
.
This provides a binding for the inner fn
to close over http-method#
and url#
. Everything in the outer fn
is local to it. Pulling it out allows for a name. Lets call it spotify-api-call
.
spotify-api-call
is a function constructor. Given the http-method
and 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.