Search code examples
clojuremacrosdestructuring

Handle destructoring in macro that creates defmethod methods


In my use case, I have a multimethod that takes two arguments, both of them maps. The first one is used, amongst other things, to compute the dispatch value. The second one is a kind of context variable that will be returned either modified or unmodified by the implementing methods.

(defmulti code (fn [input _] (:cmd input)))

; example implementation
(defmethod code "foo" [_ {var :var :as ctx}]
  {:out (str "foo; ctx.var=" var)
   :ctx ctx})

As you can see, in this example implementation I return the context variable unmodified. Since I have a lot of implementations that do not modify the context, I decided to write a macro that writes the defmethod for me, putting the computation output under the key :out and the unmodified context under :ctx:

(defmacro stable-ctx [multifn dispatch-val args impl]
  `(defmethod ~multifn ~dispatch-val ~args
     {:out ~impl
      :ctx (second ~args)}))

This works nicely for the following implementations:

(stable-ctx code "bar" [_ _] "bar")

(stable-ctx code "foo2" [_ ctx] (str "foo2; ctx.var=" (:var ctx)))

However, it is obvious that it fails if I use destructoring within my definition:

(stable-ctx code "foo3" [_ {var :var}] (str "foo3; ctx.var=" var))

(The output for (code {:cmd "foobar"} {:var 2}) is {:out "foobar; ctx.var=2", :ctx {2 :var}} instead of {:out "foobar; ctx.var=2", :ctx {:var 2}})

I would like to know how I could solve this issue. I suspect the clojure.core/destructure function could help me with this, but sadly it is missing a documentation.


Solution

  • Just use a different, private name for the context argument, and let the name the client wants. They can do whatever they want with their name, you'll still have yours.

    (defmacro stable-ctx [multifn dispatch-val [a b] impl]
      `(defmethod ~multifn ~dispatch-val [~a y#]
         (let [~b y#]
           {:out ~impl
            :ctx y#})))
    

    Here b and y# are two names for the same thing, but y# is known to be a gensymmed symbol that you created, and so is safe to return. b is whatever the client put in the arg vector, and so may be destructured if they like.