clojure-dev

Issues: https://clojure.atlassian.net/browse/CLJ | Guide: https://insideclojure.org/2015/05/01/contributing-clojure/
2020-08-06T10:37:24.494Z

I noticed the implementation of clojure.core/update-in could me made simpler by not passing the f and args to the up function. Do you think it is worth changing it?

(defn update-in
  "'Updates' a value in a nested associative structure, where ks is a
  sequence of keys and f is a function that will take the old value
  and any supplied args and return the new value, and returns a new
  nested structure.  If any levels do not exist, hash-maps will be
  created."
  {:added "1.0"
   :static true}
  ([m ks f & args]
     (let [up (fn up [m ks f args]
                (let [[k & ks] ks]
                  (if ks
                    (assoc m k (up (get m k) ks f args))
                    (assoc m k (apply f (get m k) args)))))]
       (up m ks f args))))

2020-08-06T10:48:58.494600Z

It could be

(defn update-in
  "'Updates' a value in a nested associative structure, where ks is a
  sequence of keys and f is a function that will take the old value
  and any supplied args and return the new value, and returns a new
  nested structure.  If any levels do not exist, hash-maps will be
  created."
  {:added "1.0"
   :static true}
  ([m ks f & args]
   (let [up (fn up [m ks]
              (let [[k & ks] ks]
                (if ks
                  (assoc m k (up (get m k) ks f args))
                  (assoc m k (apply f (get m k) args)))))]
     (up m ks))))

mpenet 2020-08-06T11:02:20.495900Z

@vincent.cantin did you profile it? I suspect with args coming from the closure the jvm might not be able to cache the up fn (it's pure in the original)

2020-08-06T11:02:29.496100Z

I did not profile it. You might be right.

mpenet 2020-08-06T11:02:59.496600Z

you'd get 1 fn instance per call vs 1 for all update-in calls

2020-08-06T11:09:17.496800Z

Yes, it makes sense. Thank you.

dominicm 2020-08-09T20:24:30.004400Z

You'd probably need to show your inputs for an explanation :)

Ben Sless 2020-08-10T06:25:01.005300Z

I moved it here because it diverted from the goal of the channel: https://clojurians.slack.com/archives/C03S1KBA2/p1596741797025500 In short:

(def m {:a {:b {:c {:d 1}}}})
(def ks [:a :b :c :d])
(update-in m ks inc)

devn 2020-08-16T05:19:55.008100Z

This is an old joke in Clojure as I recall. “Don’t make Rich spell out 20+ arities again”

2020-08-06T14:34:26.497400Z

It’s hard to see how the performance was improved. How did it happen?

2020-08-06T14:37:49.498Z

Oh .. I see that the new version has 1 less apply

borkdude 2020-08-06T14:41:58.499300Z

I’ve also seen benefits of avoiding apply in babashka. I wrote a macro to optimize 20 arities for this

borkdude 2020-08-06T14:42:26.000300Z

I’ve tested your simplified version and didn’t really see a performance difference compared to core

👍 1
Ben Sless 2020-08-06T18:57:32.001600Z

One thing I wonder about is why create an instance of up every time, instead of defing it as a private var? Wouldn't it save allocations and be able to be optimized by the JVM better?

borkdude 2020-08-06T18:57:55.001800Z

@ben.sless Test it and you will know

Ben Sless 2020-08-06T18:59:19.002Z

Bringing up a REPL as we speak 🙂

Ben Sless 2020-08-06T19:01:11.002200Z

now waiting for criterium to cook up an answer

Ben Sless 2020-08-06T19:05:00.002600Z

huh

Ben Sless 2020-08-06T19:05:08.002800Z

Execution time mean : 328.707164 ns
vs.
Execution time mean : 384.654215 ns

Ben Sless 2020-08-06T19:05:19.003Z

what's going on here? 😮