This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-01-14
Channels
- # adventofcode (2)
- # announcements (61)
- # babashka (26)
- # beginners (125)
- # calva (63)
- # cider (33)
- # clj-kondo (40)
- # cljs-dev (24)
- # clojure (165)
- # clojure-australia (8)
- # clojure-dev (4)
- # clojure-europe (44)
- # clojure-finland (1)
- # clojure-greece (4)
- # clojure-losangeles (1)
- # clojure-nl (28)
- # clojure-taiwan (3)
- # clojure-uk (64)
- # clojurescript (2)
- # core-async (14)
- # datomic (34)
- # docker (2)
- # fulcro (9)
- # garden (1)
- # jobs (4)
- # jobs-discuss (21)
- # kaocha (3)
- # off-topic (48)
- # pathom (4)
- # practicalli (3)
- # remote-jobs (3)
- # shadow-cljs (46)
- # spacemacs (6)
- # sql (4)
- # tools-deps (22)
- # xtdb (5)
- # yada (2)
https://gist.github.com/hiredman/a68a2add9751eb8de3d2776363219e13 is an attempt to golf a swimm style cluster membership / failure detection core.async thing that uses wrapped timeouts
I'm confused about these results - shouldn't I get a primitive array? It looks like an array of objects
(into-array Integer/TYPE [1 2 3])
;; => #object["[I" 0x39b658f6 "[I@39b658f6"]
(int-array [1 2 3])
;; => #object["[I" 0x3d5bb38f "[I@3d5bb38f"]
(in Joy of Clojure they show something like #<int[] ...
as an output, at least for the first one)Ah, so this is interesting:
(type (to-array [1 2 3]))
;; => [Ljava.lang.Object;
(type (into-array [1 2 3]))
;; => [Ljava.lang.Long;
(type (into-array Integer/TYPE [1 2 3]))
;; => [I
(type (int-array [1 2 3]))
;; => [I
(type (aget (int-array [1 2 3])
0))
;; => java.lang.Integer
I don't think so:
> (type (int 1))
java.lang.Integer
> (identical? (int 1) (Integer. 1))
false
> (identical? (int 1) (int 1))
true
I'm not sure how test for boxed Integer vs int
user=> (let [is (into-array Integer [1 2 3])] (class is))
Execution error (IllegalArgumentException) at java.lang.reflect.Array/set (Array.java:-2).
array element type mismatch
user=> (let [is (into-array Integer [(int 1) (int 2) (int 3)])] (class is))
[Ljava.lang.Integer;
user=>
Just to confirm that [I
is an array of int
As for identical?
I believe Java caches small values of numbers so (int 1)
is always the same object, but (Integer. 1)
is going to construct a new instance (with the same value) but a unique identity?
the docs say aget
/`aset` are overloaded for arrays of primitives, https://clojure.org/reference/java_interop#primitives
regarding identical?
, it looks like it just uses the java ==
operator, https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Util.java#L134
which I think is value equality for primitives like int
user=> (take 1 (drop-while #(identical? (int %) (int %)) (range)))
(128)
So the first 128 (0..127) are cached.
This is true for byte
and long
as well.
I think working with primitives is very tricky, every time arguments are passed to a function, primitives are boxed again. So I wonder if the call to type will box the return of aget
ohhh, so https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Util.java#L134 is using the ==
operator, but only after converting the ints to Integers?
because the method signature is identical(Object k1, Object k2)
rather than also having identical(int k1, int k2)
Ya, identical also boxes, all function by default will box their arguments and return value, unless they are type hinted as long or double (no other primitive type hint will work to remove the boxing)
I don't think type
can actually return a primitive type, right?
And like that article sean posted says: > You probably noticed we didnβt see the boxing occurring on the way in/out of the function. Thatβs not actually boxed math, just function invocation, so itβs a little harder to detect and warn on automatically Which is why its extra tricky, cause :warn-on-boxed won't show you if you're boxing by accident through function application
But also, in a primitive context, you can't use any of Clojure core, except for arithmetics and the array functions, everything else will box again.
also, referencing @seancorfieldβs comment about small Integers being cached. you can use that to make 1+1=3, https://pedrorijo.com/blog/java-integer-cache/
You can write functions in Clojure with (I think) up to at most 4 arguments that take a mix of Object, and long and double primitives, and return Object, or a long or double primitive, using type hints, but I don't recall are any built-in Clojure functions that do this.
And that technique works only for those primitive types, no others.
I think the "[I" indicates that it is an array of ints
@jumar `Integer` is an object. Irrelevant: Integer/TYPE
is int
which actually surprised me (I've been doing Clojure too long!)
Just saw that, yes. And the printed representation of objects has changed since JoC.
Walking a lazy seq will realize any unrealized bits, and if a lazy seq is lazily built on top of another lazy seq (like with map) then it needs to realize that lazy seq as well
Ya, I'm a little fuzzy on that myself. But basically, I know some of the overhead of walking a lazy-seq is that each next that is beyond the chunk size, we will create a new lazy-seq no?
Transducers and CollReduce are the way to pipeline operations without creating intermediate stuff between the pipeline steps
Those would assume I am starting with a collection though right? But say I have the result of (map inc [1 2 3])
Imagine I had like 100 elements in there. Now if I understand, traversing that will create intermediate representation every chunk right? And there's no way around it?
For example range has a very complicated implementation of ReduceInit (the java interface version of CollReduce) which turns reducing into a loop with a counter
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/LongRange.java#L229
Hum, so its possible that reducing over a lazy-seq will take a faster route? Like in the case of range?
So no, if you operate on it like a seq, you get it as a seq, if you use transducers you get the optimized reduce path
Clojure's reducing operations are defined over a number of different interfaces and types, it actually does a lot to try and avoid bottoming out at reducing over a sequence
You can implement ReduceInit or CollReduce and never bother with ISeq and be reducible
https://github.com/seancorfield/next-jdbc/blob/develop/src/next/jdbc/result_set.clj#L747
Yes, next.jdbc
avoids all of the overhead of creating hash maps etc if you can reduce rows to simple values -- and the reduction automatically manages the opening and closing of the connection.
If a "collection" (conceptually) knows how to reduce itself efficiently, via ReduceInit
then reduce
will go down that path, else it'll use CollReduce
which will ultimately fall back to walking the sequence if no faster approach is defined.
https://github.com/clojure/clojure/blob/master/src/clj/clojure/core/protocols.clj#L75 is where CollReduce is extended to different types/interfaces to provide faster reducing then walking seqs, Iterable for instance
Ok, thanks, I'll have a look at this. If I follow though... (map inc coll)
would not be returning something that has a faster path for reducing correct?
It would need to be (eduction (map inc) coll)
or something that uses transducers instead, or a direct call to reduce with a collection type that can be fastly reduced?
Correct, reduce is the fast path, the way to build transformation pipelines with reduce is transducers
@didibus If you would like to see the JVM objects returned by various function calls, to see what lazy seqs look like in memory, there are a couple of examples in this article using my cljol library: https://github.com/jafingerhut/cljol/blob/master/doc/README-gallery.md
Question about PersistentHashMap. If I the map contain kvpair1 and I assoc in the same pair does the amount of memory the map consumes change or is assoc smart enough to know that no change is actually needed?
(def foo {:a 1 :k2})
(assoc foo :a 1)
is the underlying storage change in any way?@bronsa Thanks!!
What is everyone using nowadays for validating ECDSA sigs
Do operations over lazy sequences in clojure work the same as in kotlin where each element goes through the whole "pipeline" at once, instead of each function being applied to the whole data in sequence?
Yes, but, I don't have enough Kotlin experience to know for sure what they do, I suspect that Kotlin is not lazy, and does something more like Java streams. So lazy-seq would not be the same. Transducers are more likely to be similar to what Kotlin does I'd suspect
That said, lazy seq will not go through the whole data in sequence, each element is pulled and then goes through the whole pipeline of transformation, and then the next element is pulled, etc.
Hum, a quick read of the Kotlin doc on sequences, so maybe they are much more like Clojure sequences.
one difference might be with chunking - clojure has chunked collections which are evaluatred N elements (often 32) at a time, rather than one by one
this won't matter if you don't use laziness for side effects (don't use laziness for side effects)
Anyways, I'm not sure on the Kotlin side, but I'm sure on the Clojure side π From Clojure, think of it as "pulling". When you define a pipeline:
(->> [1 2 3]
(map inc)
(filter odd?)
(map #(* 2 %)))
You are defining the transforms you want applied when someone requests the next element. So I'd go and say, please give me the (first ...)
element. At which point, each step buble up their request of getting the first from the previous step until you reach the collection, so the collection will return the first, then (map inc)
will increment it and pass it to (filter odd?)
. Now (filter odd?)
will see that the element is 2 and it is not odd?, so this is not the first odd? element. So filter will now ask the previous step for the second element, so (map inc)
will ask the coll for the second, gets 2, increments it to 3, passes it to (filter odd?)
which now see yup this is the first odd element, so it passes 3 to (map #(* 2 %))
which will double it and return it to you.And like noisesmith said, there's a small caveat in that because Clojure support ChunkedSeq as well (not all lazy-seq are chunked but most are). Well when you have a ChunkedSeq you can't ask for the next element, you can only ask for the next chunk. So the default chunk size is 32 it means even if you say please give me (first ...)
you're actually saying please give me the first result of the first chunk. Which means 32 elements will go through the pipeline, but not one at a time, it'll be 32 go through the first step fully, and then 32 go through the second step, etc.
This is a performance optimization, but it also means if you really want to be one at a time lazy, you have to be careful you're not using ChunkedSeq.
> you can't ask for the next element, you can only ask for the next chunk you can ask for one item, but a chunk at a time is realized and cached on the implementation side
@U016XBH746B Ya so the infographic at the end of the Kotlin doc https://kotlinlang.org/docs/reference/sequences.html this is exactly how the Clojure lazy sequence work when not chunked.
Ok, the Kotlin docs might put the Clojure docs to shame: https://kotlinlang.org/docs/reference/sequences.html
Also, I might wager that Kotlin was inspired directly by Clojure for this feature, I mean all the names are the same, Sequence, filter, map, take, etc.
Would be nice if we made a similar infographic to explain lazy-seq, chunkedseq, transducers, etc... Hum, add it to my backlog of todos that I never get too π
well the model is way more complicated once you factor in chunking and transients
but filing an issue on https://github.com/clojure/clojure-site/issues with concrete requests is a great step. we have resources to do this kind of work when time allows (more skillful people than me :)
I have a question about the path set by deps.edn. Our deps.edn file includes:
:test
{:extra-paths ["test"]
:extra-deps {com.cognitect/test-runner {:git/url ""
:sha "209b64504cb3bd3b99ecfec7937b358a879f55c1"}}
:main-opts ["-m" "cognitect.test-runner"]}
However, itβs not getting included when running: clj -M:dev:test
I keep getting:
Execution error (FileNotFoundException) at clojure.main/main (main.java:40).
Could not locate cognitect/test_runner__init.class, cognitect/test_runner.clj or cognitect/test_runner.cljc on classpath. Please check that namespaces with dashes use underscores in the Clojure file name.
When I look at my path with clj -Spath -M:dev:test
then I donβt see anything appropriate. My colleagues execute this and their paths include:
.gitlibs/libs/com.cognitect/test-runner/209b64504cb3bd3b99ecfec7937b358a879f55c1/src
But I donβt have this in my path, and I donβt even have a .gitlibs directory.
Any suggestions for where I can track this down please?Does clojure -Sforce -M:dev:test
solve the problem?
clojure -Sdescribe
-- what version of the CLI are you running?
{:version "1.10.1.492"
:config-files ["/usr/local/Cellar/clojure/1.10.1.492/deps.edn" "/Users/pgearon/.clojure/deps.edn" "deps.edn" ]
:config-user "/Users/pgearon/.clojure/deps.edn"
:config-project "deps.edn"
:install-dir "/usr/local/Cellar/clojure/1.10.1.492"
:config-dir "/Users/pgearon/.clojure"
:cache-dir ".cpcache"
:force false
:repro false
:resolve-aliases ""
:classpath-aliases ""
:jvm-aliases ""
:main-aliases ""
:all-aliases ""}
Yup, that's too old for the new -M
behavior.
that's really old
1.10.1.763 is current. A lot of changes happened after 1.10.1.536
https://clojure.org/releases/tools lists all the changes.
While itβs trying to pull it in now, itβs failing to do so:
Cloning:
Error building classpath. Destination path "test-runner" already exists and is not an empty directory
I canβt find a directory by this name anywhere. Could it be a different directory that I should be trying to clean out?I suspect that's in ~/.gitlibs
either you had a failed thing there that's messing with it or you are encountering a known race condition with downloading gitlibs
adding -Sthreads 1
avoids the latter for the moment
OK, Iβve hurt myself here. I blew away the ~/.gitlibs directory, but now it says:
Cloning:
Error building classpath. [email protected]:cognitect-labs/test-runner.git: USERAUTH fail
it's not finding your user auth. 2fa isn't specifically an issue
https://clojure.org/reference/deps_and_cli#_git has some further tips
first, check
ssh-add -l
to ensure it's finding your identity in the agentthen most thing wrong is in ~/.ssh/config
using IdentityFile ~/.ssh/id_rsa
there is usually problematic - try commenting that out (with #
)
hang on, I think it's probably something else
you're using https (public) but it's trying git (private)
are you on CI or local?
do you have a ~/.gitconfig with a insteadOf
directive?
enabled with something like git config --global url."[email protected]:".insteadOf
(don't run that, but that's how it would be enabled)
grep for insteadOf in ~/.gitconfig
could also be set on a per-project basis
yeah, that's forcing it to try to use your ssh key
I suspect you're using an older version where -M
doesn't bring in dependencies (it used to just run :main-opts
).
Anyone know how to create a middleware-like pattern using Jetty Servlets?
I gave a go to HandlerWrapper
and HandlerCollection
, but they are a bit different from what I expected: when the original (i.e. wrapped) handler completes, the HTTP connection is succesfully terminated with its result.
Whereas I'm seeking something more similar to Ring middleware, where a given member can alter the previous member's body/status/headers prior to HTTP termination.
it looks like it because of the commonly used idioms for wrapping handlers in middleware
it has been a long time since I've used servlets, I think the equivalent for them is something like a servlet that takes another "inner" servlet when constructed, and then whenever its doGet or whatever method is called, fiddles with the parameters it gets, maybe replacing some streams with bytearrayinputstreams or whatever, and then passes those parameters to the "inner" servlet, possibly waiting around for it to complete, then examining the baos and doing whatever
I see. Sounds very fiddly indeed :) if this kind of transparent composition isn't encapsulated under a handy technique or class like HandlerWrapper/HandlerCollection I'm not sure I'd want to give it a shot.
Closest thing I found (just now) is HttpOutput.Interceptor
. In principle it only alters bodies, although maybe status/headers will remain mutable using usual methods
hello, one question on performance, I'm trying to make my custom version of reduce-kv
, before adding my things to the process I'm trying to figure what is the fastest way to iterate on a map, other than reduce-kv
, I ran a few experiments, the fastest option I found out was using the map iterator directly, but that still 1.2x slower than reduce-kv
, is there a simple way to loop on a map that's closer in performance to reduce-kv
?
this my iterator based implementation:
(defn kv-loop-iterator [f init coll]
(let [it (clojure.lang.RT/iter coll)]
(loop [out init]
(if (.hasNext it)
(let [entry (.next it)
k (key entry)
v (val entry)]
(recur (f out k v)))
out))))
bench results (also add some other things I tried):
| title | mean | bar | variance-str |
|-------------------------+---------------+----------------------+--------------|
| reduce-kv-native | 136.966296 ns | ββββββ | 0.000x |
| reduce-kv-loop-iterator | 301.646678 ns | ββββββββββββ | 1.202x |
| reduce-kv-simple-reduce | 412.819192 ns | ββββββββββββββββ | 2.014x |
| reduce-kv-loop-seq | 530.523741 ns | ββββββββββββββββββββ | 2.873x |
I think youβre unlikely to beat reduce-kv, or self reduction on a map
Maps donβt store entries, just kvs. So anything that makes entries that you have to take apart is just additional cost in making and destroying objects
reduce-kv avoids that
On the clojure survey, what does "namespaces" relate to in Q22. Rate the priority of making improvements to Clojure in these areas.
I took it as meaning "all things related to namespaces" -- so if there's anything around how we write/use namespaces, mark how important that is for you, and then add a specific comment in the free text box on the last page.
I took the same as @seancorfield, one example that comes to my mind is this issue: https://clojure.atlassian.net/browse/CLJ-2123
Working on that right now, coming in 1.11... :)
@U064X3EF3 Great to hear! Can you say how it's going to be addressed?
All in good time
You know me: as soon as there are changes on master (or another accessible branch), I'm happy to try them out at work! π
well obviously nothing before 1.10.2 ... likely rc3 coming tomorrow
Will get it on dev a.s.a.p. and onto QA early next week...
Thanks