Fork me on GitHub

Really appreciate this, as I’m preparing for


is clojure directly linked for performance, or also so third-party libs can’t mess with clojure internally?

Alex Miller (Clojure team)21:01:19

We”re cool with messing :)


I believe the motivation was primarily the small but measurable performance improvements.


The fact that things like tools.trace no longer work for core functions calling other core functions seems like primarily a disadvantage to me, but it is pretty easy to build a non-direct-linked version of Clojure if you want to enable that.

Alex Miller (Clojure team)21:01:52

We build and publish the slim one for that so no need to build

👍 10

caveat: I'm going from memory and observation-from-a-distance here, not actual knowledge of the full reasons.


The reason I’m asking: I’m running into an issue with instrumenting hash-map which doesn’t work in Clojure, because spec uses binding in spec-checking-fn and that call is expanded into a non-directly linked call of hash-map which then results in a loop. A solution would be to inline the hash-map call. If the argument was that third-party libs can’t mess with var redefs, than that would align with that change and there would be an extra motivation. e.g.

$ clj
Clojure 1.10.0
user=> (binding [*print-length* 1] (println (range 10)))
(0 ...)
user=> (alter-var-root #'clojure.core/hash-map (fn [v] (fn [& kvs])))
#object[user$eval138$fn__139$fn__140 0x5f7b97da "user$eval138$fn__139$fn__140@5f7b97da"]
user=> (binding [*print-length* 1] (println (range 10)))
Execution error (NullPointerException) at user/eval144 (REPL:1).


Would building your own non-direct-linked version of Clojure help you with your current purposes, even if others did not have that version of Clojure readily available via Maven/Clojars?


no, it would make this issue worse 🙂


the point here is that the call to hash-map is not happening against the directly linked version


if you write (binding [*print-length* 1] ...) there is going to be a call to the var #'hash-map


Oh, because hash-map is used in the implementation of spec itself?


because binding is used in the implementation of spec (spec-checking-fn)


Yeah, using spec / trace / etc. on functions that are themselves low enough in the implementation that the implementations of those mechanisms rely upon them makes my brain hurt. I know you press on sometimes in spite of that hurt 🙂


I found a nice performance improvement in binding but it didn’t solve the issue:

user=> (time (dotimes [_ 10000000] (binding [*print-length* 10])))
"Elapsed time: 3754.649969 msecs"
user=> (time (dotimes [_ 10000000] (binding* [*print-length* 10])))
"Elapsed time: 1356.306343 msecs"


If you’re interested, the speedup comes from creating the hash-map in binding at macro-expansion time


but I’m no longer sure if I diagnosed the issue correctly, since that didn’t help 🙂 the perf improvement could be worth a ticket maybe


Oh, wait, it does solve the issue, but it’s not the only one


it has the potential to execute side effecting expressions in a different order


it being what?


constructing a hash map at macro expansion time


not something code should rely on I think


sure, but it is a potential backwards incompatibility to make a judgement call about


I wonder who is using side-effects in binding and relying on the order.


When clojure.core/hash changed from Clojure 1.5.0 to Clojure 1.6.0, and thus the order of return for seq changed for hash based maps and sets, there were a smattering of example-based tests on projects, including some of Clojure's, that needed updating because they began failing.


I do not recall any of them intentionally relying on the order, but they had repeatable results as long as the hash function remained the same.


For the insanely curious, here was one of those commits to restore some at-that-time-recently-deleted-tests:


@andy.fingerhut yes, I remember that. I also had to deal with that. We used a datomic query function that relied on the order of map-entries, it was very bad to begin with and our pain was deserved.


binding is probably not as pervasively used as seq on maps and sets, but where it is used, perhaps that is the kind of change in behavior hiredman is raising a flag about.


why is/can the order of map-entries be different at compile time btw?


because the iteration order on clojure's hash maps is based on the trie structure which is based on the hash of the key which is, to the degree in which it is a good hash function, random


so when write code in some order in a text file, and a macro or the read turns it in to a hash map, then hands it to the compiler, the compiler will iterate it in some order based on the hash, not the order in the text file


@hiredman I’m aware that the order of side effects can happen in a different order than written down. E.g.

#{(do (println 1) 1) (do (println 2) 2) (do (println 3) 3)}
#{1 3 2}
My question was about the difference in order when creating a map compile time or run-time in binding. Can you give an example when this would matter?


(I’m afk now… late here)