This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-01-26
Channels
- # beginners (145)
- # boot (19)
- # calva (61)
- # cider (33)
- # cljs-dev (15)
- # cljsrn (16)
- # clojure (35)
- # clojure-dev (39)
- # clojure-russia (2)
- # clojure-spec (10)
- # clojure-uk (7)
- # clojurescript (5)
- # cursive (14)
- # data-science (1)
- # datomic (3)
- # figwheel-main (9)
- # fulcro (46)
- # jobs (4)
- # off-topic (8)
- # quil (6)
- # re-frame (5)
- # shadow-cljs (70)
- # spacemacs (3)
- # speculative (2)
- # tools-deps (2)
Really appreciate this, as I’m preparing for https://2019.flatmap.no/talks/assum
is clojure directly linked for performance, or also so third-party libs can’t mess with clojure internally?
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.
We build and publish the slim one for that so no need to build
“slim” is the maven classifier http://central.maven.org/maven2/org/clojure/clojure/1.10.0/
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 ...)
nil
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).
null
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?
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?
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"
nil
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
sure, but it is a potential backwards incompatibility to make a judgement call about
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: https://github.com/clojure/clojure/commit/91dd867b4229a31d4d915aece97f41b3811cf4d4
@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.
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
#{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?