Cond-> with multiple values

I see quite a few situations where a vector of two (or even three) values ​​needs to be "updated" if a certain condition is met, and otherwise left alone. Example:

(let [val1 some-value
      val2 some-other-value
      [val1, val2] (if something-true 
                       (first-calculation val1 val2 some-other-arg)
                       [val1, val2])
      [val1, val2] (if something-else-true 
                       (second-calculation some-other-arg val1 val2)
                       [val1, val2])
      ...etc...)

      

where the assumption is that the first calculation and the second calculation return a vector [val1, val2] with possible updated values.

This style of code is not only clunky, but it probably also has some extra overhead of creating and destroying the vector every time.

Does anyone have any suggestion on how to improve this code with vanilla Clojure without creating a macro? In other words, I am looking for some kind of cond-> for multiple values.

+3


source to share


1 answer


To do this, we can copy a trick often seen in graphics processing and other use cases where functions always take a context map as the first arg (or, in our case, a context vector). Try to rewrite it like this. Note the change in args to second-calculation

:

(defn first-calculation
  [ctx               ; first arg is the context (vec here, usually a map)
   some-other-arg]
  (let [[val1 val2] ctx]  ; destructure the context into locals
       ...
    [val1-new val2-new] ))   ; return new context

(defn second-calculation
  [ctx               ; first arg is the context (vec here, usually a map)
   some-other-arg]
  (let [[val1 val2] ctx]  ; destructure the context into locals
    ...
    [val1-new val2-new] ))   ; return new context

(let [ctx [some-value some-other-value]
   (cond-> ctx
      something-true       (first-calculation  some-other-arg)
      something-else-true  (second-calculation some-other-arg)
      ...etc... ))

      


Here's a more specific example:



(defn inc-foo [ctx amount]
  (let [{:keys [foo bar]} ctx
        foo-new (+ foo amount)
        ctx-new (assoc ctx :foo foo-new)]
       ctx-new ))

(defn inc-bar [ctx amount]
  (let [{:keys [foo bar]} ctx
        bar-new (+ bar amount)
        ctx-new (assoc ctx :bar bar-new)]
     ctx-new ))

(dotest
  (loop [i   0
         ctx {:foo 0 :bar 0}]
    (let [{:keys [foo bar]} ctx
          >>      (println (format "i =%2d   foo =%3d   bar =%3d   " i foo bar))
          ctx-new (cond-> ctx
                    (zero? (mod i 2)) (inc-foo i)
                    (zero? (mod i 3)) (inc-bar i))]
         (if (< 9 i)
           ctx-new
           (recur (inc i) ctx-new)))))

      

with the result:

i = 0   foo =  0   bar =  0   
i = 1   foo =  0   bar =  0   
i = 2   foo =  0   bar =  0   
i = 3   foo =  2   bar =  0   
i = 4   foo =  2   bar =  3   
i = 5   foo =  6   bar =  3   
i = 6   foo =  6   bar =  3   
i = 7   foo = 12   bar =  9   
i = 8   foo = 12   bar =  9   
i = 9   foo = 20   bar =  9   
i =10   foo = 20   bar = 18   

      

Perhaps you could write a macro like (with-context [foo bar] ...

to remove part of the template if you've used that a lot.

+6


source







All Articles