clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
Adam Kalisz 2021-06-18T00:03:20.033300Z

Just watched @tcrayford video https://www.youtube.com/watch?v=0tUrbf6Uzu8 What is the performance of small collections in Clojure now? @alexmiller @ztellman @ptaoussanis do we still need https://clojure.atlassian.net/browse/CLJ-1517 and https://clojure.atlassian.net/browse/CLJ-1610 ?

Adam Kalisz 2021-06-18T00:06:00.034400Z

Perhaps an update of this talk would be in order 😉 Some performance tips on ClojureScript would also be great.

ghadi 2021-06-18T00:11:53.035400Z

The unrolled vecs/maps were a case of bad benchmarking

ghadi 2021-06-18T00:12:32.036Z

Something that sounded good but didn’t prove out in the wild

Adam Kalisz 2021-06-18T00:17:45.037100Z

Ok, seems to be still half open/ there is no closing words for that proposal if I understand it correctly.

ghadi 2021-06-18T00:21:57.039500Z

IIRC, the benchmarks were oriented around monomorphic calls, which HotSpot excels at optimizing, but the increased # of classes seen at critical call sites (e.g. RT/assoc) turned those call sites megamorphic. Those kinds of call sites have the worst performance

ghadi 2021-06-18T00:23:07.040600Z

microbenchmarks stress monomorphism... real world programs have more dynamism

ghadi 2021-06-18T00:23:30.041Z

It would be nice to close those tickets with a nice summary

ghadi 2021-06-18T00:24:31.041600Z

some really good analysis of a similar failed optimization effort here https://github.com/google/guava/issues/1268 @adam.kalisz

ghadi 2021-06-18T00:24:58.042300Z

> Guava ImmutableList (and others) offer awful performance in some cases due to size-optmized specializations

Adam Kalisz 2021-06-18T00:27:33.044600Z

Thanks! I have added a comment below the YouTube talk. I would appreciate a summary, these tickets leave a sour taste when the case wasn't so clear after all. 🙂

alexmiller 2021-06-18T00:34:18.047200Z

I don’t think the idea is dead. Rich would still love to have tuples. They just need to be performant. There are lots of new tools in Java since this work that may offer new choices

1👍
seancorfield 2021-06-18T00:34:50.047300Z

@adam.kalisz I think Rich’s comments in CLJ-1517 are pretty clear on the performance problems (but CLJ-1610 could benefit from a comment at least pointing to why CLJ-1517's original approach wasn’t accepted).

1👍
seancorfield 2021-06-18T00:35:29.047800Z

(and it’s nice to see the link to the Guava issue — in Colin’s comment on CLJ-1517 — I had stopped following the issue by that point I think so it’s very interesting to read about that failed optimization too!)

alexmiller 2021-06-18T00:49:42.048500Z

It was weirdly contemporaneous iirc

2021-06-18T02:28:23.048700Z

I don't think anything about Hash Array Mapped Trie is needed for the O(1) count though, Clojure just literally counts elements as calls to assoc are made.

ghadi 2021-06-18T02:46:56.049800Z

I don't think anyone has tried v8-style or SELF-style hidden classes in Clojure

ghadi 2021-06-18T02:48:13.051200Z

(such a map optimization would still have to defeat the megamorphic issue mentioned above)

Ovidiu Stoica 2021-06-18T02:54:55.052100Z

In learning clojure, what is the ONE thing that if you practice regularly, you will become a a great clojure programmer?

seancorfield 2021-06-18T02:56:19.052200Z

You might get better responses in #beginners since folks there have opted in to helping/teaching new folks…

seancorfield 2021-06-18T02:56:59.052400Z

But, to answer your question: REPL-Driven Development.

6➕
seancorfield 2021-06-18T02:59:33.052800Z

Always work in your editor, never type into the REPL. Always evaluate every change you make as you make it (you don’t even need to save the file!). Use Rich Comment Forms (i.e., (comment ..)) for all of your “scratch” ideas and exploration — and usually keep it in place in the final code so you can see how you got there. Be able to run tests via the REPL from your editor, so you’re not switching contexts. Develop good REPL hygiene so you can keep a REPL running for days (or weeks, or even months) without restarts or “refresh” tooling.

11
Ovidiu Stoica 2021-06-18T03:01:59.053100Z

Thank you, @seancorfield! I will ask in #beginners but thank you a lot for the response!

coby 2021-06-18T04:03:54.053500Z

> Always work in your editor, never type into the REPL. As an autodidact I dabbled in Clojure for at least a year before I really understood this, or REPL-driven development generally. Wish I'd heard it stated plainly like this! 😅

seancorfield 2021-06-18T04:05:46.053700Z

I hadn’t really thought about it being an issue until Stu Halloway said in one of his talks that he was “always baffled when he saw people typing into the REPL”…

seancorfield 2021-06-18T04:06:39.053900Z

https://github.com/matthiasn/talk-transcripts/blob/master/Halloway_Stuart/REPLDrivenDevelopment.md — “Save everything. Your interactions with the REPL -- I am baffled when people type things into the REPL. We will talk about that a bit more in a second.”

1🎉
seancorfield 2021-06-18T04:06:52.054200Z

(the whole talk is excellent)

coby 2021-06-18T04:21:36.054600Z

Thanks, I'll check it out.

coby 2021-06-18T04:24:20.054800Z

> Develop good REPL hygiene so you can keep a REPL running for days (or weeks, or even months) without restarts or “refresh” tooling. While we're on the subject, do you have any recommended reading about this? I've been using mount and generally getting better at this but my progress has been pretty piecemeal. Usually have to restart my REPL at least once a day to hack around various things I don't fully grok yet.

coby 2021-06-18T04:31:47.055Z

hah, he's drinking a Lagunitas...I think I'll go grab a beer 😎

vemv 2021-06-18T04:45:50.055200Z

> In learning clojure, what is the ONE thing that if you practice regularly, you will become a a great clojure programmer? Jumping to the source to understand what you're invoking. And favoring the use of said source over querying documentation, issue trackers, etc

seancorfield 2021-06-18T04:57:45.055500Z

@ctamayo “While we’re on the subject, do you have any recommended reading about this?” Well, there’s the whole https://clojure.org/guides/repl/introduction series — especially the last two sections (Enhancing…, Guidelines…). Stu’s talk is excellent, @holyjak just posted a video of RDD (in the #news-and-articles channel: https://clojurians.slack.com/archives/C8NUSGWG6/p1623857070131800 ) and that thread contains more links — including Stu’s talk and my demo to London Clojurians from December showing RDD. And there’s also a link in that thread to this set of resources: https://clojure.org/guides/repl/annex_community_resources

11
zendevil 2021-06-18T06:03:44.057Z

@didibus can you point me to the loc’s where this mutation on count’s value happens?

Ben Sless 2021-06-18T06:09:17.059200Z

With regards to unrolling, I did find significant performance improvements when unrolling rest-args or keys sequences when they're known at call site. The difference between (get-in m [k1 k2]) and (-> m (get k1) (get k2)) is significant and surprising. Same with assoc

2021-06-18T15:02:37.068100Z

You might enjoy looking at https://github.com/redplanetlabs/specter which has similar performance to unrolled, but has an interface which is even nicer than the higher-level clojure functions, especially when dealing with highly-nested data.

Ben Sless 2021-06-18T15:36:38.068400Z

I'm familiar with spectre but my design goal was to stick as close as possible to Clojure's semantics, with the ability of providing a drop in replacement, so users don't have to learn a new language. My implementation passes the core test suit.

dpsutton 2021-06-18T06:11:41.059500Z

get-in being faster i'm assuming?

Ben Sless 2021-06-18T06:12:36.059700Z

about 2x slower

Ben Sless 2021-06-18T06:14:17.060200Z

you can see the results here: https://github.com/bsless/clj-fast/blob/master/doc/results.md#get-in It's basic loop unrolling

Ben Sless 2021-06-18T06:14:53.060900Z

not to mention that iterating over the keys with reduce1 is slower than reduce

2021-06-18T06:18:03.061Z

Ya, if you look here: https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentHashMap.java#L139 For maps, each assoc implementation returns a new map with the count parameter set to count + 1 and here for vector on cons is the same: https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentVector.java#L228

coby 2021-06-18T06:26:07.061500Z

Thanks so much, really appreciate your efforts. 🙂

1
2021-06-18T06:46:28.063400Z

Did you check if it has that same megamorpgic issue the ticket refers too for real-life use cases? (No idea what megamorpgic refers too so I don't really know of relevant to get-in)

2021-06-18T07:22:51.063900Z

https://en.m.wikipedia.org/wiki/Inline_caching jitted jvm method calls will emit certain instruction sequences depending on if a call is monophonic (only invoked with a single target type), polymorphic (some small, maybe 2, different target types), or megamorphic (many target types, full real dispatch through a method table)

2021-06-18T07:22:51.063900Z

https://en.m.wikipedia.org/wiki/Inline_caching jitted jvm method calls will emit certain instruction sequences depending on if a call is monophonic (only invoked with a single target type), polymorphic (some small, maybe 2, different target types), or megamorphic (many target types, full real dispatch through a method table)

stardiviner 2021-06-18T10:40:04.064700Z

I got error Clojure can't invoke "java.util.concurrent.Future.get()" because "fut" is null.

stardiviner 2021-06-18T10:41:13.066Z

Here is the details, the source code caused error:

(defn long-func []
  (let [p (promise)]
    (.start (Thread. (fn []
                       (Thread/sleep 5000)
                       (deliver p "hello!"))))))

;;; this expr will wait for 5 seconds.
(deref (long-func))
And here is the stacktrace:
Show: Project-Only All 
  Hide: Clojure Java REPL Tooling Duplicates  (13 frames hidden)

1. Unhandled java.lang.NullPointerException
   Cannot invoke "java.util.concurrent.Future.get()" because "fut" is null

                  core.clj: 2304  clojure.core/deref-future
                  core.clj: 2324  clojure.core/deref
                  core.clj: 2310  clojure.core/deref
                      REPL:    8  user/eval7232
                      REPL:    8  user/eval7232
             Compiler.java: 7181  clojure.lang.Compiler/eval
             Compiler.java: 7136  clojure.lang.Compiler/eval
                  core.clj: 3202  clojure.core/eval
                  core.clj: 3198  clojure.core/eval
    interruptible_eval.clj:   87  nrepl.middleware.interruptible-eval/evaluate/fn/fn
                  AFn.java:  152  clojure.lang.AFn/applyToHelper
                  AFn.java:  144  clojure.lang.AFn/applyTo
                  core.clj:  667  clojure.core/apply
                  core.clj: 1977  clojure.core/with-bindings*
                  core.clj: 1977  clojure.core/with-bindings*
               RestFn.java:  425  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   87  nrepl.middleware.interruptible-eval/evaluate/fn
                  main.clj:  437  clojure.main/repl/read-eval-print/fn
                  main.clj:  437  clojure.main/repl/read-eval-print
                  main.clj:  458  clojure.main/repl/fn
                  main.clj:  458  clojure.main/repl
                  main.clj:  368  clojure.main/repl
               RestFn.java: 1523  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   84  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:   56  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:  152  nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
                  AFn.java:   22  clojure.lang.AFn/run
               session.clj:  202  nrepl.middleware.session/session-exec/main-loop/fn
               session.clj:  201  nrepl.middleware.session/session-exec/main-loop
                  AFn.java:   22  clojure.lang.AFn/run
               Thread.java:  831  java.lang.Thread/run
My computer environment: - operating system: macOS Big Sur (Version 11.4) (M1 Apple Sillicon) - Clojure version: “1.10.3” - Java version: OpenJDK 17

Karol Wójcik 2021-06-18T10:41:42.066100Z

You have to return the promise

Karol Wójcik 2021-06-18T10:41:49.066300Z

Which you're not doing

Karol Wójcik 2021-06-18T10:42:08.066500Z

(defn long-func []
  (let [p (promise)]
    (.start (Thread. (fn []
                       (Thread/sleep 5000)
                       (deliver p "hello!")))
p
)))

stardiviner 2021-06-18T10:42:29.066800Z

I see. Let me take a try. Thanks @karol.wojcik

stardiviner 2021-06-18T10:50:00.067Z

Problem solved. the returned p need to put out side of (.start …) sexp. Thanks a lot! I thought this might be a bug…. how stupid.

chrisn 2021-06-18T13:32:28.067300Z

The memory model stuff (memory segments, writing to native memory) I don't use much aside to get an integer pointer as I already use sun.misc.unsafe for writing/reading to native memory. The ffi stuff, CLinker and friends I use when I generate jdk-16 FFI bindings and I do this using a library named insn https://github.com/cnuernber/dtype-next/blob/master/src/tech/v3/datatype/ffi/mmodel.clj. There could conceivably be a JDK-16 specific version of dtype-next that doesn't use unsafe at all and thus doesn't required restricted access but some parts of the memory model specification - regions - I have absolutely no use for as dtype-next uses the https://github.com/techascent/tech.resource to bind objects that need a dispose-or-free-type operation to the GC or stack frame as needed.

chrisn 2021-06-18T13:32:57.067600Z

One thing to note as that graalvm has two completely different and unrelated FFI pathways of which I support one of.

2021-06-18T14:38:04.067900Z

unrelated to your question, the core future function returns a dereffable thing to get the result of a thread (like you have here) but with proper dynamic binding behavior and using a thread pool for efficiency

2021-06-18T15:02:37.068100Z

You might enjoy looking at https://github.com/redplanetlabs/specter which has similar performance to unrolled, but has an interface which is even nicer than the higher-level clojure functions, especially when dealing with highly-nested data.

Ben Sless 2021-06-18T15:36:38.068400Z

I'm familiar with spectre but my design goal was to stick as close as possible to Clojure's semantics, with the ability of providing a drop in replacement, so users don't have to learn a new language. My implementation passes the core test suit.

stardiviner 2021-06-18T16:33:46.068600Z

I see. Thanks for your suggestion. I will learn.

2021-06-18T16:52:39.074800Z

Hey guys, Clojure programming in Emacs, any tips? Of course I know about Cider, but apart from that? I understand the Clojure people are keen on ParInfer (for Emacs that'd be https://github.com/justinbarclay/parinfer-rust-mode then I imagine) more so than on the default Emacs paredit? (I'm yet to try ParInfer, I used to use Lispy and the Lispy EVIL thing before when working in Common/Emacs Lisp.) I'm an EVIL user (the Vim emulation for Emacs that is). So working in multiple modes is really preferred, being all the time in insert mode is really not my preference. Still – I'm happy to try anything that has potential.

Darrick Wiebe 2021-06-20T01:01:08.219200Z

I like to use both paredit and parinfer at the same time. For me parinfer is awesome for straight-line coding but awful for significant editing, where paredit really shines. Parinfer also has two modes, and I find it indispensible to be able to toggle easily between them, so I added this lifesaver keybinding:

(evil-global-set-key 'insert (kbd "C-p") 'parinfer-toggle-mode)
(evil-global-set-key 'normal (kbd "C-p") 'parinfer-toggle-mode)
I can see which mode I'm in because the strict mode has rainbow parens visible, and the regular mode does the grey-out parens thing, which I find very intuitive, but took some work to get working. I also ran into problems with the rust variant of parinfer so fell back to the original which is slightly slower on huge files but in practice is never a problem. I agree with the above comment on learning some basic paredit commands, but would encourage you to gradually learn them all 🙂 Even the weirdo C-? is awesome about once every couple of days...

1👍
2021-06-24T16:07:31.397200Z

in my experience parinfer is more popular for people without lisp experience(?) but ymmv, and agreed that parinfer is great when first writing the code but becomes inconvenient when editing later (but I haven't touched it in years)

dpsutton 2021-06-18T16:55:52.076400Z

I am not a fan of parinfer and stay with paredit. Simple, always works, and I direct it rather than it trying to figure out what indentation i need. it seems basically backwards. things should indent on the nesting i provide, not assume the nesting based on indentation. as for modal editing, I don't know of any reason that you should have to switch to non-modal editing

4✅
isak 2021-06-18T17:08:11.076700Z

You can become pretty productive if you just learn 2-3 paredit type commands. Some that I use all the time that I recommend learning the keybinds for: • Slurp forwards (makes it easy to surround code with an if/when/let, etc) • Splice Sexp killing backwards (makes it easy to undo the above) • Kill sexp

1👍
2021-06-18T17:24:25.078500Z

I think the main thing with clojure people being keen on parinfer is that it's a useful tool for beginners to get into the language if they're unused to lispy syntax. Almost every clojure developer I've spoken to who started with parinfer has "graduated" to other structural editing packages.

lspector 2021-06-19T17:23:04.179300Z

FWIW I regularly teach new Clojure programmers and I strongly discourage parinfer. I think it is actively counterproductive, although I appreciate that it is clever and that some people like it. What I seek both for the new Lispers I teach and for myself (a lisper for 35+ years) is something that acts as close as possible to a generic text editor, but with (crucially) good bracket matching and auto-re-indentation. No parinfer, no paredit, nothing that gets in the way of existing typing/cutting/pasting habits.

2021-06-18T17:29:45.079900Z

Right, I wasn't aware of that. So basically on front of structural editing, I might as well stick to Lispy/Lispyville then.

1🎯
2021-06-18T17:30:32.081Z

So structural editing + Cider would then be the typical way of working with Clojure in Emacs then I reckon?

emccue 2021-06-18T17:33:07.081800Z

@suskeyhose I've been using parinfer for years now. Can't work with anything else productively

emccue 2021-06-18T17:34:02.082700Z

its the main reason I haven't really tried calva

seancorfield 2021-06-18T17:50:59.087300Z

I’ve gone back and forth between Paredit and Parinfer multiple times over the 11 years I’ve been doing Clojure. I like some aspects of both and I dislike some aspects of both. For the last several years, I was using them together, first in Atom, then in VS Code, but now I’m using Calva (+ Chlorine) so I’m only using Paredit. I think Paredit takes a lot more getting used to and it’s a lot more powerful — but you need to learn/remember a lot more commands to be productive. I found Parinfer mostly did what I expected/needed but missed the more powerful structural stuff that Paredit adds. When I used them together, I disabled some of Paredit’s indent/paste stuff in order to let Parinfer do its thing.

1🚀
pez 2021-06-18T17:53:21.089600Z

Calva Peredit plays pretty well with the Parinfer extension. You'll need to disable Calva's indent-as-you-type, though.

isak 2021-06-18T18:12:43.092500Z

In cursive with paredit, you can be up to normal code-editing productivity, if not higher, just by knowing 1 keybind (Ctrl-W for "Expand selection")

1👆
blak3mill3r 2021-06-18T18:16:33.092700Z

That is how I work. Lispy(ville) + Cider. I think if you can get really good lispy muscle-memory, that is about as good as any structural editing support out there.

1👍
afleck 2021-06-18T18:38:48.093300Z

i’ll second lispy(ville), it’s nice.

kennytilton 2021-06-18T20:19:43.094800Z

@ben.sless https://www.youtube.com/watch?v=Yt4zQqndLdQ Every annual Clojure survey trashes the stack traces. Java? Excellent choice of bar to clear! 👏 Sadly, the real bar is set by Common Lisp. @p-himik Yes, old Google hits should be vacuumed, agreed. The weird thing? No one asked how I saved the sale. Or congratulated me on doing so. You yobbos never change....

kennytilton 2021-06-18T21:28:30.098200Z

I just live off indentation and re-indentation, and parens-balancing highlighting. I know how to edit, thanks. But I have been Lisping for 25 years... I had a fling with Paredit, not sure what ended the relationship. I might have forgotten its birthday...

2👍1😂
kennytilton 2021-06-18T21:29:51.099500Z

Hmm, The Google is failing me. I saw some ADR templates somewhere, cannot find it now. Any recommendations? 🙏

2021-06-18T23:57:58.100900Z

I found aggressive-indent-mode + adjust-parens-mode in Emacs is my favourite combo. Works better than paredit and pareinfer for me. Though I use a few smartparens commands on top.