Reusing destructuring for multiple methods

Is there a way to reuse destructuring between multiple methods in a multimethod?

(defmulti foo (fn [x] (:a x)))
(defmethod foo :1 [{:keys [a b c d e]}] (str a b c d e))
(defmethod foo :2 [a] "")
(defmethod foo :3 [a] "")

      

Now this is a trivial example, but imagine that we have much more complex destructuring with nested maps and I want to use it in all mine defmethods

for foo. How should I do it?

+3


source to share


1 answer


A practical solution would be to only use the keys you need for each individual method. The important thing to note about destructuring is that you don't have to bind each value in the collection's destruction. Let's say that every collation passed to this multimethod contains keys :a

through :e

, but you only need a pair of such keys for each method. You can do something like this:

; note: a keyword can act as a function; :a here is equivalent to (fn [x] (:a x))
(defmulti foo :a)  
(defmethod foo :1 [{:keys [a b c d e]}] (str a b c d e))
(defmethod foo :2 [{:keys [b d]}] (str b d))
(defmethod foo :3 [{:keys [c e a]}] (str a c e))

      

If you have a complex nested structure and want to get certain values, you can just leave the keys you don't need, or alternatively, depending on your use case, the binding let

in the function definition might end up easier to read. Steve Losh Comes to the Rescue of Clojure Cave - Writing a rogue adventure game from scratch in Clojure, he used nested cards to represent the state of the game. He originally wrote some functions using destructuring to access the internal bits of the "game state" map, for example:

(defmethod draw-ui :play [ui {{:keys [tiles]} :world :as game} screen]
  ...

      

But then later , he decided to make this code more readable by pulling destructuring into the let binding:

(defmethod draw-ui :play [ui game screen]
  (let [world (:world game)
        tiles (:tiles world)
        ...

      

The point is, if you are working with a deeply nested structure and you want your code to be simple (especially if you are writing a multimethod with multiple methods using the same structure as the argument), you may want to avoid using destructuring and just use bindings let

to capture the desired fragments. get-in

is a good tool for getting accurate values ​​from nested collections. Going back to the Clojure caves for example, if Steve just needs tiles, he could do something like this:



(defmethod draw-ui :play [ui game screen]
  (let [tiles (get-in game [:world :tiles])
    ...

      

Personally, I find this much easier to read than deactivating function arguments with {{:keys [tiles]} :world :as game}

.


EDIT:

If you really want to avoid repeating destructuring for each multimethod and want each method to have the same bindings, you can write a macro:

(defmulti foo :a)

(defmacro deffoomethod [dispatch-val & body]
  `(defmethod foo ~dispatch-val [{:keys [~'a ~'b ~'c ~'d ~'e]}]
     ~@body))

(deffoomethod 1 (str a b c d e))
(deffoomethod 2 (str b d))
(deffoomethod 3 (str a c e))

(foo {:a 1 :b 2 :c 3 :d 4 :e 5})
;=> "12345"

(foo {:a 2 :b \h :d \i})
;=> "hi"

(foo {:a 3 :b \x :c 0 :d \x :e 0})
;=> "300"

      

I would not recommend this approach as it breaks macro photography. Anyone using this macro should remember that it associates characters a

via e

with the corresponding keys in the argument, and this can be problematic.

+5


source







All Articles