Fork me on GitHub
#clojure
<
2021-01-14
>
hiredman00:01:40

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

jjttjj00:01:25

cool, thanks! you always have the best core.async gists

jumar04:01:51

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)

jumar05:01:05

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

jumar05:01:48

the last one with aget - is that always an object?

phronmophobic05:01:35

I don't think so:

> (type (int 1))
java.lang.Integer

phronmophobic05:01:25

> (identical? (int 1) (Integer. 1))
false
> (identical? (int 1) (int 1))
true

phronmophobic05:01:01

I'm not sure how test for boxed Integer vs int

seancorfield05:01:12

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

seancorfield05:01:22

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?

πŸ‘ 6
phronmophobic05:01:57

the docs say aget /`aset` are overloaded for arrays of primitives, https://clojure.org/reference/java_interop#primitives

phronmophobic05:01:12

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

phronmophobic05:01:22

which I think is value equality for primitives like int

seancorfield05:01:26

user=> (take 1 (drop-while #(identical? (int %) (int %)) (range)))
(128)

seancorfield05:01:37

So the first 128 (0..127) are cached.

seancorfield05:01:25

This is true for byte and long as well.

didibus05:01:43

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

phronmophobic05:01:09

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?

phronmophobic05:01:35

because the method signature is identical(Object k1, Object k2)

phronmophobic05:01:50

rather than also having identical(int k1, int k2)

didibus05:01:11

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)

πŸ‘ 3
didibus05:01:31

Unless they also have an overload in Java, like aget does

seancorfield05:01:37

I don't think type can actually return a primitive type, right?

didibus05:01:29

I guess it couldn't, since it probably can't actually be passed one as argument

jumar05:01:10

(a great discussion, thanks!)

didibus05:01:06

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

didibus05:01:22

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.

phronmophobic05:01:34

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/

seancorfield05:01:08

Hahaha... πŸ™‚

😈 6
andy.fingerhut14:01:32

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.

andy.fingerhut14:01:53

And that technique works only for those primitive types, no others.

phronmophobic05:01:53

I think the "[I" indicates that it is an array of ints

seancorfield05:01:04

@jumar `Integer` is an object. Irrelevant: Integer/TYPE is int which actually surprised me (I've been doing Clojure too long!)

jumar05:01:28

Yeah, I think it makes sense - see more examples in the thread

πŸ‘ 3
seancorfield05:01:54

Just saw that, yes. And the printed representation of objects has changed since JoC.

πŸ‘ 3
didibus05:01:01

Is it possible to walk a lazy-seq without creating intermediate data?

didibus05:01:04

Say I know I'll consume it all, and just want to sum the numbers in it.

hiredman05:01:47

It depends on what you been by "lazy-seq" and "intermediate data"

hiredman05:01:23

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

didibus05:01:30

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?

hiredman05:01:04

Transducers and CollReduce are the way to pipeline operations without creating intermediate stuff between the pipeline steps

didibus05:01:52

Those would assume I am starting with a collection though right? But say I have the result of (map inc [1 2 3])

didibus05:01:48

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?

hiredman05:01:04

They do not assume that

hiredman05:01:21

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

hiredman05:01:38

It has a complicated implementation, the ReduceInit impl is pretty simple

didibus06:01:32

Hum, so its possible that reducing over a lazy-seq will take a faster route? Like in the case of range?

didibus06:01:25

Even when its wrapped like above in a call to map?

hiredman06:01:50

Range can return a complicated object

hiredman06:01:42

One that is a seq, but also has a separate code path for optimized reducing

hiredman06:01:40

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

hiredman06:01:22

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

hiredman06:01:58

You can implement ReduceInit or CollReduce and never bother with ISeq and be reducible

hiredman06:01:29

I think next.jdbc does that for things like result sets

seancorfield06:01:58

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.

seancorfield06:01:21

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.

hiredman06:01:29

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

didibus06:01:33

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?

didibus06:01:21

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?

hiredman07:01:25

Correct, reduce is the fast path, the way to build transformation pipelines with reduce is transducers

didibus07:01:56

Sounds good, thanks

andy.fingerhut14:01:26

@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

πŸ‘€ 3
Steven Katz14:01:41

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?

Steven Katz14:01:21

(def foo {:a 1 :k2})
(assoc foo :a 1)
is the underlying storage change in any way?

bronsa14:01:15

the current impl returns the original map if both k and v are already present

bronsa14:01:20

but it's an impl detail

bronsa14:01:29

not a guarantee

kiranshila16:01:57

What is everyone using nowadays for validating ECDSA sigs

GGfpc19:01:02

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?

didibus19:01:46

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

didibus19:01:49

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.

GGfpc19:01:46

Awesome, that's exactly what I wanted to know, thanks!

didibus19:01:35

Hum, a quick read of the Kotlin doc on sequences, so maybe they are much more like Clojure sequences.

didibus19:01:41

And not like Java streams

noisesmith19:01:24

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

πŸ‘ 3
noisesmith19:01:43

this won't matter if you don't use laziness for side effects (don't use laziness for side effects)

didibus19:01:31

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.

didibus19:01:33

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.

didibus19:01:40

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.

didibus19:01:12

\end-wall-of-text

noisesmith19:01:43

> 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

πŸ’― 3
didibus19:01:11

> you're actually saying please give me the first result of the first chunk

didibus19:01:56

@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.

didibus19:01:51

Ok, the Kotlin docs might put the Clojure docs to shame: https://kotlinlang.org/docs/reference/sequences.html

didibus19:01:58

Damn 😞

didibus19:01:05

I'm talking specifically of their infographics

didibus19:01:45

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.

didibus20:01:45

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 πŸ˜›

Alex Miller (Clojure team)20:01:33

well the model is way more complicated once you factor in chunking and transients

Alex Miller (Clojure team)20:01:06

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 :)

quoll20:01:53

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?

seancorfield20:01:36

Does clojure -Sforce -M:dev:test solve the problem?

quoll20:01:02

Just tried… and no

seancorfield20:01:29

clojure -Sdescribe -- what version of the CLI are you running?

quoll20:01:13

{: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 ""}

seancorfield20:01:29

Yup, that's too old for the new -M behavior.

quoll20:01:50

ah, thank you

seancorfield20:01:52

1.10.1.763 is current. A lot of changes happened after 1.10.1.536

quoll20:01:21

I’ve only been paying attention to the 1.10.1

quoll20:01:33

Yup, pulling it in now. Thank you!

quoll20:01:58

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?

Alex Miller (Clojure team)20:01:34

I suspect that's in ~/.gitlibs

quoll20:01:52

thank you!

Alex Miller (Clojure team)20:01:11

either you had a failed thing there that's messing with it or you are encountering a known race condition with downloading gitlibs

Alex Miller (Clojure team)20:01:22

adding -Sthreads 1 avoids the latter for the moment

quoll20:01:07

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

quoll20:01:38

maybe it’s my 2FA on github

Alex Miller (Clojure team)20:01:35

it's not finding your user auth. 2fa isn't specifically an issue

quoll20:01:16

Thank you

Alex Miller (Clojure team)20:01:35

first, check

ssh-add -l
to ensure it's finding your identity in the agent

Alex Miller (Clojure team)20:01:58

then most thing wrong is in ~/.ssh/config

Alex Miller (Clojure team)20:01:27

using IdentityFile ~/.ssh/id_rsa there is usually problematic - try commenting that out (with #)

Alex Miller (Clojure team)21:01:14

hang on, I think it's probably something else

Alex Miller (Clojure team)21:01:24

you're using https (public) but it's trying git (private)

Alex Miller (Clojure team)21:01:48

are you on CI or local?

quoll21:01:17

sorry, I don’t follow that question

quoll21:01:29

oh, continuous integration

quoll21:01:38

this is all my local command line

Alex Miller (Clojure team)21:01:22

do you have a ~/.gitconfig with a insteadOf directive?

Alex Miller (Clojure team)21:01:47

enabled with something like git config --global url."[email protected]:".insteadOf

Alex Miller (Clojure team)21:01:34

(don't run that, but that's how it would be enabled)

Alex Miller (Clojure team)21:01:50

grep for insteadOf in ~/.gitconfig

Alex Miller (Clojure team)21:01:07

could also be set on a per-project basis

quoll21:01:15

ah, yes, I do. Last set up some years ago, so I forgot it

Alex Miller (Clojure team)21:01:34

yeah, that's forcing it to try to use your ssh key

quoll21:01:13

Finally got it. Thank you!

quoll21:01:19

(actually running the tests now)

quoll21:01:25

appreciate it all. That was a lot πŸ™‚

seancorfield20:01:01

I suspect you're using an older version where -M doesn't bring in dependencies (it used to just run :main-opts).

☝️ 3
vemv23:01:06

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.

hiredman23:01:24

that isn't actually how ring middle works

hiredman23:01:57

it looks like it because of the commonly used idioms for wrapping handlers in middleware

hiredman23:01:19

but a ring middleware is (fn [f] (fn [req] (f req)))

hiredman23:01:04

given a handler, return a new handler that applies the given handler to a request

hiredman23:01:17

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

vemv23:01:04

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

wilkerlucio23:01:53

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?

wilkerlucio23:01:10

this my iterator based implementation:

wilkerlucio23:01:11

(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))))

wilkerlucio23:01:25

bench results (also add some other things I tried):

wilkerlucio23:01:27

| 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       |

Alex Miller (Clojure team)00:01:23

I think you’re unlikely to beat reduce-kv, or self reduction on a map

Alex Miller (Clojure team)00:01:27

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

Oliver George23:01:00

On the clojure survey, what does "namespaces" relate to in Q22. Rate the priority of making improvements to Clojure in these areas.

seancorfield23:01:20

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.

wilkerlucio23:01:11

I took the same as @seancorfield, one example that comes to my mind is this issue: https://clojure.atlassian.net/browse/CLJ-2123

Alex Miller (Clojure team)00:01:04

Working on that right now, coming in 1.11... :)

seancorfield00:01:37

@U064X3EF3 Great to hear! Can you say how it's going to be addressed?

seancorfield00:01:39

You know me: as soon as there are changes on master (or another accessible branch), I'm happy to try them out at work! πŸ™‚

Alex Miller (Clojure team)01:01:17

well obviously nothing before 1.10.2 ... likely rc3 coming tomorrow

3
seancorfield02:01:13

Will get it on dev a.s.a.p. and onto QA early next week...