Fork me on GitHub
#clojure
<
2020-11-04
>
solf11:11:43

I'm going to give a (remote) introduction of clojure for some coworkers. I'll check the popular plugins for their editors (mostly vscode I think), but some of them are non-programmers. For them, I'm looking into the easiest to setup clojure ide. Maybe even something on cloud. I looked into http://repl.it but it doesn't allow to eval a single expression, only the whole file, and that seems so limiting when it comes to lisps.

tugh11:11:13

Why not just use default repl for non-programmers? Which features of IDE you want to use? IMHO, just using the plain repl will make things easier in terms of tooling and setup issues.

tugh11:11:57

BTW I've tried cursive and I'm currently using vscode(with calva and clj-kondo). vscode feels easier for me.

solf11:11:53

Using the repl directly is something that I didn't even think of doing, but actually makes sense, specially at the beginning. It's a good way to make them understand that the ide isn't doing much more than the line you're evaluating to the repl.

💪 3
solf11:11:18

But it's quite limiting, and not really a fun way to program, IMO. My main goal is to have them continue coming to the clojure lessons 😅

🙃 3
dominem12:11:17

IMHO, it'd be better to do a quick intro to vscode + calva, at least to show how to set it up and then proceed to clojure. Hmm.. before vscode you can also do a quick intro to the plain repl itself, then say that there are much better tools for clojure dev and then show them vscode. You'll figure it out 🙂

calva 3
Ben Sless13:11:15

- I think there's a way to connect jupyter notebooks with Clojure - Reveal might make the experience more pleasant for them

pez15:11:07

Once leinigen or clj is installed, Calva is for sure a pretty easy step. But for people who aren’t devs, getting Java and lein installed can be quite daunting…

solf19:11:15

That is true... I've also never installed clojure outside of Linux, and it's remote so hard to help. I'm actually considering using babashka instead.

solf19:11:04

Thanks for the suggestions. I'm an emacs user, so I'll install vscode + calva by myself to see how easy it is and if I can guide people through it.

pez20:11:51

I was thinking if babashka might be the way to go. I've been wanting to bundle it with Calva. That would remove so much friction towards just trying the language out. Anyway, if you use babashka, what you need to do is to start it with its nrepl option and then connect Calva. When is this intro to happen?

solf22:11:59

It's still not decided, could be in a week or in a month. I actually just got news two hours ago that I'm allowed to fly back to Singapore, where my work is located. This would mean I could do it in person, in small groups of 5 people max (covid is under pretty good control there).

solf22:11:07

Bundling babashka with calva would be great! Maybe as an optional download.

nha12:11:41

Java interrop question.. How can I call .drainTo in Clojure on a BlockingQueue? Attempt below:

(import '[java.util.concurrent LinkedBlockingQueue BlockingQueue]
          '[java.util ArrayList])

  (let [^BlockingQueue q (doto (LinkedBlockingQueue.)
                           (.put [:command 1])
                           (.put [:command 2])
                           (.put [:command 3]))
        coll (ArrayList.)]


    ;; 
    ;; drainTo(Collection<? super E> c)
    ;; Removes all available elements from this queue and adds them to the given collection.


    (.drainTo coll)
    ;; FAIL
    ;; Unhandled java.lang.IllegalArgumentException
    ;; No matching field found: drainTo for class java.util.ArrayList

    ;; FAIL
    ;; (.drainTo (make-array (type []) 3))
    ;; Unhandled java.lang.IllegalArgumentException
    ;; No matching field found: drainTo for class [Lclojure.lang.PersistentVector;

    coll)

flowthing13:11:43

That should be (.drainTo q coll), I think. See also https://clojure.org/reference/java_interop

nha13:11:58

gah of course! thanks

nha13:11:13

works perfectly 👌:skin-tone-4:

borkdude14:11:35

This might be an unimportant performance thing, but why isn't destructure using find + val here: https://github.com/clojure/clojure/blob/7baa5b2f79f05c56a04e1d5c90129972cd962df3/src/clj/clojure/core.clj#L4465-L4466

user=> (let [defaults {:a 1}] (time (dotimes [i 100000000] (val (find defaults :a)))))
"Elapsed time: 379.921527 msecs"
nil
user=> (let [defaults {:a 1}] (time (dotimes [i 100000000] (contains? defaults :a) (defaults :a))))
"Elapsed time: 759.108181 msecs"

Alex Miller (Clojure team)14:11:27

feel free to file a jira

dpsutton14:11:11

wow. crazy how different the speeds of (defaults :a) (:a defaults) and (get defaults :a) are

borkdude14:11:36

Actually the or defaults are usually symbols:

user=> (let [defaults '{a 1}] (time (dotimes [i 100000000] (contains? defaults 'a) (defaults 'a))))
"Elapsed time: 1583.053364 msecs"
nil
user=> (let [defaults '{a 1}] (time (dotimes [i 100000000] (second (find defaults 'a)))))
"Elapsed time: 6325.311779 msecs"

Alex Miller (Clojure team)14:11:06

they are always symbols

borkdude14:11:33

yeah, and in the case of symbols the current implementation is way faster. hmm!

borkdude14:11:35

nope, sorry, I was using second which causes the slowness:

user=> (let [defaults '{a 1}] (time (dotimes [i 100000000] (val (find defaults 'a)))))
"Elapsed time: 697.04199 msecs"
nil
user=> (let [defaults '{a 1}] (time (dotimes [i 100000000] (contains? defaults 'a) (defaults 'a))))
"Elapsed time: 1593.39499 msecs"
nil

borkdude14:11:51

so yeah, I'll file a JIRA. yay!

Alex Miller (Clojure team)14:11:04

I mean those numbers make sense to me based on what they're doing

Alex Miller (Clojure team)14:11:18

the first is one lookup, the second is two

borkdude14:11:34

yes, that's why I wondered. so JIRA good?

Alex Miller (Clojure team)14:11:20

could you try to make a perf test that actually uses destructure ?

ghadi14:11:33

is this causing an issue in real world code?

borkdude14:11:11

it's not, but why not take the perf if it doesn't make the code any less readable

ghadi14:11:34

because it's not costless to accept changes

ghadi14:11:52

this is only called at macroexpansion time, right?

borkdude14:11:54

> This might be an unimportant performance thing Alex responded: feel free to file a jira. He also could have responded with: not important, bye.

borkdude14:11:34

in sci I do call it at runtime

borkdude14:11:06

but I'll make a perf test with destructure itself and I'll let it sit there until it's closed in 2028 ;)

ghadi14:11:26

please don't be sarcastic

ghadi14:11:28

core team works hard to manage issues

borkdude15:11:13

again, I asked the core team (Alex) if this was unimportant and the reaction was: feel free to file a JIRA. That's what I did, ok?

ghadi15:11:20

there is a non-issue, imho -- lots of macrotime things use the not most performant code

borkdude15:11:21

I would also have accepted: no

ghadi15:11:24

(like last)

borkdude15:11:35

macroexpansion has impact on startup time which is not unimportant I would say

Alex Miller (Clojure team)15:11:20

I think it's worth doing - we have chased micro gains in this area in the past given how common destructuring is

ghadi15:11:11

you are misreading the ticket @alexmiller

ghadi15:11:41

it's not the code emitted by destructuring that is being optimized (which would be worthwhile)

ghadi15:11:02

it's the destructuring macro helper itself

Alex Miller (Clojure team)15:11:17

I'm ok with bringing it to rich, he can decide

borkdude15:11:23

I'm accepting any outcome, I just found this while fixing a bug in sci. I appreciate the hard work the core team is doing. In the grand scheme of things this may not matter very much and that's ok. I don't find a noticable difference with:

(time (dotimes [i 10000000] (destructure '[{:keys [a] :or {a 1}} {:b 1}])))
(time (dotimes [i 10000000] (destructure2 '[{:keys [a] :or {a 1}} {:b 1}])))
where destructure2 is the patched one.

borkdude15:11:37

I'll add this to the ticket.

borkdude15:11:38

I do see potential speedups in the generated code, e.g. replacing get with a keyword lookup when :keys is used, etc, but I haven't considered this longer than core probably has. So I'll leave it at this for now

borkdude15:11:53

I didn't mean this sarcastically btw @U050ECB92 . There's so many trade-offs to make and probably this has been considered ages ago.

ghadi15:11:35

no worries.

ghadi15:11:46

i'm salty about the election and running on far less sleep than usual

💜 3
borkdude15:11:00

I can imagine... hoping for a good outcome...!

ghadi15:11:43

(:foo m) emits a lot of code, but is faster

ghadi15:11:05

emit a lot of code -> might make the surrounding method uninlinable

ghadi15:11:19

indy would make that go away, but interferes with graal native image

ghadi15:11:46

i'm hoping project leyden will have a hook for rewriting indy

borkdude15:11:48

what's indy?

ghadi15:11:52

invokedynamic

borkdude15:11:51

I see an issue about that here: https://github.com/oracle/graal/issues/2762 Seems like they are working on it. Also the MethodHandle problem in Reflector.java JDK11 would be solved with that.

borkdude15:11:25

> Note that the performance of such method handles will be slower compared to the Java HotSpot VM because no dynamic compilation of method handle chains is possible.

ghadi15:11:38

interesting, didn't realize https://github.com/oracle/graal/issues/2761 was in progress

borkdude15:11:55

Yeah, that would be cool. Currently I work around the problem in Reflector.java like this: https://github.com/borkdude/clj-reflector-graal-java11-fix

fadrian15:11:11

I am wanting to replace an old Java service (slow and somewhat buggy) with Clojure. The interface to this system inputs/writes out several XML files marshallled and unmarshalled by JAXB-annotated code. I have the .xsd files defining the 250 or so classes that can be in-/output by this service (and of course, the generated Java classes). Does Clojure have any tools to make this replacement simpler or am I stuck updating the old Java code? So far, I've run across libraries like clj-xsd, which parses .xsd files, but gives no help as to reading/writing the XML data files that might be marshalled/unmarshalled by JAXB. And, although there are one-or-two libraries that mention JAXB, they seem to be moribund/aborted. Has anyone even attempted something like this in the past, or is this a data format that Clojure doesn't have a very good answer for?

roklenarcic15:11:30

I think most clojure solutions deserialize XML to nested maps, obviating the need for classes generated from xsd

roklenarcic15:11:17

nested maps do require more memory than plain java objects

roklenarcic15:11:28

alternatively you can keep JAXB and annotated classes and you can deserialize to those objects, but then use Clojure to process them

fadrian16:11:30

That's what I've sort of figured. The sheer number of classes potentially contained by the XML is giving me pause, though. I was thinking if there was some sort of way to generate Clojure code from the .xsd files for marshalling/unmarshalling the XML to records - which can be used like maps, my internal processing would potentially be a bit simpler and I could avoid manually generating the XML output.

ghadi16:11:16

do you need to sign the xml?

ghadi16:11:30

cryptographically

fadrian17:11:39

No. I don't.

didibus03:11:41

Hum.... Not sure a Record uses any less memory then a map. I'd ignore the "fear of maps using more memory" and just parse the XML into maps

fadrian22:11:15

Thanks all for your suggestions. They've been helpful. I'll spend a bit more hammock time on this.

p-himik16:11:52

clojure.data/diff-associative is implemented as:

(defn- diff-associative
  "Diff associative things a and b, comparing only keys in ks (if supplied)."
  ([a b]
     (diff-associative a b (set/union (keys a) (keys b))))
  ([a b ks]
     ...))
But (set/union (keys a) (keys b)) when a and b are both regular maps can result in duplicate values because keys does not return a set-like object:
(clojure.set/union (keys {1 1}) (keys {1 1}))
=> (1 1)

p-himik16:11:32

So it seems like diff might do some useless work because it violates clojure.set/union contract.

noisesmith16:11:14

yeah, calling set/union on non-set inputs is a bug

andy.fingerhut17:11:46

Yes, this is the one place I know of in Clojure's core code that it would violate an imagined spec on clojure.set/union that it only takes sets as inputs.

borkdude18:11:08

TIL:

user=> (defn foo:bar [])
#'user/foo:bar
Is this "supported" behavior? I found it here: https://github.com/littleredcomputer/sicmutils/blob/6dc57d74e6f2ee22688b061012b14fa314636086/src/sicmutils/series.cljc#L58

😆 3
borkdude18:11:57

It wouldn't surprise me if this was an edge case in the reader

Darin Douglass18:11:22

per the reader docs, it seems like it's supported: > Symbols beginning or ending with ':' are reserved by Clojure. A symbol can contain one or more non-repeating ':'s. > https://clojure.org/reference/reader#_symbols

borkdude18:11:36

'foo::bar
doesn't work though

bronsa18:11:54

one or more non repeating

borkdude18:11:19

Argh, thanks :) Reading is hard.

9
dpsutton19:11:38

best pun i've seen in a month

☝️ 3