This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2024-02-13
Channels
- # announcements (1)
- # babashka (28)
- # beginners (72)
- # biff (6)
- # calva (15)
- # clerk (14)
- # clj-otel (4)
- # cljdoc (4)
- # clojure (121)
- # clojure-europe (61)
- # clojure-nl (2)
- # clojure-norway (63)
- # clojure-uk (5)
- # datahike (35)
- # datalevin (37)
- # datomic (7)
- # emacs (2)
- # fulcro (6)
- # gratitude (1)
- # honeysql (2)
- # hyperfiddle (38)
- # malli (9)
- # matrix (24)
- # meander (4)
- # off-topic (10)
- # polylith (8)
- # reagent (2)
- # releases (1)
- # shadow-cljs (8)
- # spacemacs (4)
- # specter (1)
- # squint (5)
- # tools-deps (3)
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
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”
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
these Tidy Tuesday posts are great: https://codewithkira.com/clojure-tidy-tuesdays/
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"])
if Java will add any overloads then your program will crash with those newer Java versions if you haven't disambiguated already
To be fair, the first example would be (map #(.toUpperCase ^String %) ["hi" "there"])
without reflection
true, in the linked blog post the type hint wasn't there so it didn't seem that much better
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)
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
> 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
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 🪙 🪙> 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?
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.
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
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?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.(map String/toUpperCase ["hi"] [Locale/CANADA])
IDK why I chose Canada, it was just first on the listThe 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
clj-kondo could help here I think (since it already knows there's two overloads, for this particular class)
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?).
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?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).
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.
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.
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.
I sometimes wonder whether future
is a bit like pmap
in that it is more easy than simple. Perhaps a bit too easy.
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.
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.
Indeed. 👍 They're very nice for quickly trying things out in the REPL, but perhaps they're best avoided in most production code.
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.
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).