Fork me on GitHub
#clojure-europe
<
2024-02-13
>
Mario Trost07:02:42

Good morning! Was working with criterium/quick-bench for the first time yesterday, what a pleasure. Is there something as convenient to measure memory allocation per function call? Similar to this here: https://clojure-goes-fast.com/blog/clojures-deadly-sin/

;;;; Transducers+sequence

(time+
 (doall
  (sequence (comp (map inc)
                  (map inc)
                  (map #(* % 2))
                  (map inc)
                  (map inc))
            (repeat 1000 10))))

;; Time per call: 86.16 us   Alloc per call: 102,776b

metal 1
Mario Trost07:02:32

Found this https://github.com/clojure-goes-fast/clj-memory-meter but don’t think I can use it for my pupose as is states that it “allows you to inspect how much memory an object occupies at runtime”

Mario Trost06:02:41

To resolve this and for future searchers looking for measuring ram usage / memory allocation for single function calls, I first went with https://clojure-goes-fast.com/kb/profiling/clj-async-profiler/ But even more convenient was the time+ macro https://clojure-goes-fast.com/kb/benchmarking/time-plus/ That was more than sufficient for my use case

thomas08:02:55

Good morning lovely people!

dharrigan08:02:30

Good Morning!

otfrom08:02:48

madainn mhath

schmalz09:02:58

Morning all.

otfrom09:02:54

these Tidy Tuesday posts are great: https://codewithkira.com/clojure-tidy-tuesdays/

👀 1
Ben Sless09:02:09

tfw fipp uses satisfies? on the hot path 😿

maleghast09:02:53

madainn mhath :flag-scotland:

borkdude10:02:45

I'm looking at the new https://insideclojure.org/2024/02/12/method-values/ and asking myself: How is

(map #(.toUpperCase ^String %) ["hi" "there"])
any worse than
(map ^[] String/toUpperCase ["hi" "there"])

Ben Sless10:02:59

Does it need the type hint in the second case?

borkdude10:02:00

if there would be only 1 method (no overloads) then not

borkdude10:02:34

if Java will add any overloads then your program will crash with those newer Java versions if you haven't disambiguated already

😬 1
borkdude10:02:58

To be fair, the first example would be (map #(.toUpperCase ^String %) ["hi" "there"]) without reflection

borkdude10:02:05

but the benefit seems marginal to me

Ben Sless10:02:33

It's already less characters

Ben Sless10:02:36

About 10% better

Ben Sless10:02:44

(depends on how you count)

borkdude10:02:45

true, in the linked blog post the type hint wasn't there so it didn't seem that much better

vemv10:02:15

I feel generally unenthusiastic about these changes, however being fair, String/toUpperCase seems to read more nicely for humans and machines alike .toUpperCase ^String % is quite the contraption, normally I write (-> ^String % .toUpperCase) just to keep the order 'right' and String/toUpperCase seems simpler to process for tools, it's one sexpr vs. arbitrarily many. If one finds the newer syntax one can shortcircuit all heuristics (in cider land we have a fine 'context' parser, but making things simpler seems a win for everyone)

vemv10:02:01

I haven't checked at all, but I wonder if ^[] String/toUpperCase offers stronger compile-time guarantees? Often with old-style ^ you can specify incorrect hints and there will be no/late errors

imre11:02:41

> if Java will add any overloads then your program will crash with those newer Java versions if you haven't disambiguated already I find this a compelling counter-argument against wildcards, and inference in general

imre11:02:19

are the core team aware of this?

imre11:02:24

They mist have considered it

genRaiy11:02:40

In this example, I like the obviousness and directness that is not present in the former more magical interop

(map ^[] String/toUpperCase ["hi" "there"])
That' my 🪙 🪙

borkdude11:02:43

> are the core team aware of this?

Ed11:02:12

> It is not possible to supply individual values to var arg methods in either invocation or value position, although this may be considered in a future release. > For me, fixing this would be the biggest benefit. I agree that the current set of changes aren't really much of an improvement. It's easy enough to warn on reflection when you care. I agree that the qualified syntax reads more consistently with Clojure's require ... :as but it's just more stuff to learn. 🤷 ... Maybe down the line there'll be an option to not wrap it in a function and unlock some JVM jit optimisations?

pez11:02:09

I remember being totally surprised about that I couldn’t apply on Java methods the first time(s) I tried it. Haven’t considered the implications with overloading, but seems good to make the language more intuitive.

💯 1
borkdude11:02:04

user=> (apply ^[] String/toUpperCase ["hi"])
"HI"
user=> (apply String/toUpperCase ["hi"])
Syntax error (IllegalArgumentException) compiling at (REPL:1:1).
Multiple matches for method toUpperCase in class java.lang.String, use param-tags to specify

Ben Sless11:02:40

(apply Math/pow [2 3])
8.0
Nice

❤️ 1
slipset12:02:02

It’s kind’a interesting that String/toUpperCase has two arities. and in the map context, ie,

(map String/toUpperCase ["hi"])
Should resolve cleanly, since there is only one possible version to call?

slipset12:02:36

Might very well be hard to generally understand how many seqs you’re mapping over, though, like,

(apply map String/toUpperCase lols)
Or some degenerate version of it.

Ben Sless12:02:11

(map String/toUpperCase ["hi"] [Locale/CANADA])
IDK why I chose Canada, it was just first on the list

slipset12:02:28

Sure, but now you know which arity you should choose?

Ben Sless12:02:54

Yes, but this is the sort of inference Clojure doesn't do (yet) and could

👍 1
slipset12:02:11

Yes, exactly, I guess 😉

pez12:02:08

The unhinted apply example is vastly better than what we had before:

(apply String/toUpperCase ["hi"])
; Syntax error compiling at (.calva/output-window/output.calva-repl:1463:1).
; Unable to find static field: toUpperCase in class java.lang.String
Multiple matches for method toUpperCase in class java.lang.String, use param-tags to specify is very actionable. And once the LLMs catch up it will be even easier to figure out what the next step is. And maybe clj-kondo (possible with a quick-fix in clojure-lsp) can help me even before I compile? Even better with the hinted 😃
(apply ^[] String/toUpperCase ["hi"])
; Syntax error reading source at (REPL:1467:11).
; Metadata must be Symbol,Keyword,String or Map

borkdude12:02:04

clj-kondo could help here I think (since it already knows there's two overloads, for this particular class)

seancorfield17:02:08

We updated to Alpha 7 as soon as it dropped so we have it running in our QA/Staging environments and I went through all our #(\.instanceMethod ..) examples and most of the ones we might want to change are multi-argument and in test code where we don't care about reflection, so there's no point in changing them (the code doesn't have type hints but adding the class name and potentially param tags would make the code quite a bit bigger). However, there were a few in source code which I updated, and one particular case where I had comp and three interop calls all wrapped as anonymous functions with type hints -- so that was a nice cleanup using the new syntax. Looking at our codebase, I think Functional Interface support will buy us a lot more than method values -- but I think even method values will encourage us to use interop more and plain wrappers less so that will be a net win overall. It's a shame they've decided to punt varargs to 1.13 (presumably) but I suspect they want the new interop stuff to get some production usage and feedback before they pile too much stuff into a single release (1.12 has been in development now for close to two years I think?).

upvote 1
1
🙏 1
grav10:02:58

I always thought that eg java.util.Date. was just syntactic sugar for /new - but while

(java.util.Date. 1707771694522)
works fine, I need to use params-tag with /new in Clojure 1.12:
(^[long] java.util.Date/new 1707771694522)
Why can't the same (I guess run-time) logic be used for the latter?

grav10:02:47

And how would I use /new with Clojure 1.11 at all?

grav10:02:19

Ah ... the dot-syntax is https://clojure.org/reference/java_interop#_alternative_macro_syntax for (new ...) which I never used admittedly 🙂. /new is a https://clojure.org/news/2024/02/08/1-12-alpha6#method_values in 1.12 (no pun).

grav10:02:05

Still a bit puzzled by the choice for no inference on invocation. It's explicitly mentioned in https://insideclojure.org/2024/02/12/method-values/: > :param-tags metadata must be supplied any time there are multiple arities or overloads - no inference is done. This may be more verbose than the older syntax, but the tradeoff is specificity. You know there is no inference and no reflection But then there's the exception wrt static methods ... and so it's a bit confusing ... I'd probably have expected Clojure to just help me along and compromise speed.

seancorfield16:02:34

The whole point of the new feature is no reflection: you can continue to use the old syntax if you don't care about performance and don't want to type hint.

seancorfield16:02:43

While you can use the new syntax for invocation, it is most useful to avoid the wrappers currently needed to pass a function as a value.

💯 1
Ed11:02:01

Morning

flowthing12:02:46

I sometimes wonder whether future is a bit like pmap in that it is more easy than simple. Perhaps a bit too easy.

flowthing12:02:50

It is a bit too easy to use a future without dereferencing it, and if the code in the future throws an exception, you'll never know about it.

Ed12:02:22

agreed. I tend to think of them as a reference type rather than a mechanism for running things in the background. Like promise/`deliver` is really easy to create deadlocks, the way in which you want to retrieve the value is important.

flowthing12:02:59

Indeed. 👍 They're very nice for quickly trying things out in the REPL, but perhaps they're best avoided in most production code.

borkdude12:02:34

same goes for memoize

👍 1
Ed12:02:20

true. That's a can of worms too. I remember Alex saying something about writing a Clojure puzzler's / gotcha's type book. I wonder how that's going.

seancorfield17:02:04

We have a logged-future macro that we use instead of plain future so at least we still get logging (and associated ERROR handling). We def. overused future in our early code (we started back in 2011 so quite a bit of our Clojure codebase is over a decade old now).

❤️ 1
flowthing17:02:52

Heh, I just implemented exactly the same macro in our codebase for the exact same reason. 🙂