I am trying to implement a huge Java interface with numerous (~50) getter and setter methods (some with irregular names). I thought it would be nice to use a macro to reduce the amount of code. So instead of
(def data (atom {:x nil}))
(reify HugeInterface
(getX [this] (:x @data))
(setX [this v] (swap! data assoc :x v)))
I want to be able to write
(def data (atom {:x nil}))
(reify HugeInterface
(set-and-get getX setX :x))
Is this set-and-get macro (or something similar) possible? I haven't been able to make it work.
(Updated with a second approach -- see below the second horizontal rule -- as well as some explanatory remarks re: the first one.)
I wonder if this might be a step in the right direction:
NB. that the
atom-bean
macro passes the actual compile-time value ofemit-atom-g&ss
on toreify-from-maps
. Once a particularatom-bean
form is compiled, any subsequent changes toemit-atom-g&ss
have no effect on the behaviour of the created object.An example macroexpansion from the REPL (with some line breaks and indentation added for clarity):
Two
macroexpand-1
s are necessary, becauseatom-bean
is a macro which expands to a further macro call.macroexpand
would not be particularly useful, as it would expand this all the way to a call toreify*
, the implementation detail behindreify
.The idea here is that you can supply an
emit-map
likeemit-atom-g&ss
above, keyed by keywords whose names (in symbolic form) will trigger magic method generation inreify-from-maps
calls. The magic is performed by the functions stored as functions in the givenemit-map
; the arguments to the functions are a map of "implicits" (basically any and all information which should be accessible to all method definitions in areify-from-maps
form, like the name of the atom in this particular case) followed by whichever arguments were given to the "magic method specifier" in thereify-from-maps
form. As mentioned above,reify-from-maps
needs to see an actual keyword -> function map, not its symbolic name; so, it's only really usable with literal maps, inside other macros or with help ofeval
.Normal method definitions can still be included and will be treated as in a regular
reify
form, provided keys matching their names do not occur in theemit-map
. The emit functions must return seqables (e.g. vectors) of method definitions in the format expected byreify
: in this way, the case with multiple method definitions returned for one "magic method specifier" is relatively simple. If theiface
argument were replaced withifaces
and~iface
with~@ifaces
inreify-from-maps
' body, multiple interfaces could be specified for implementation.Here's another approach, possibly easier to reason about:
This calls on the compiler at runtime, which is somewhat expensive, but only needs to be done once per set of interfaces to be implemented. The result is a function which takes an atom as an argument and reifies a wrapper around the atom implementing the given interfaces with getters and setters as specified in the
get-set-map
argument. (Written this way, this is less flexible than the previous approach, but most of the code above could be reused here.)Here's a sample interface and a getter/setter map:
And some REPL interactions: