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)
Dumb question about the new method values - will (map String/toUpperCase ["hi" "there"])
work, and use reflection, but be slower, and (map ^[] String/toUpperCase ["hi" "there"])
recommended for speed, or will only the second option work.
It was fewer characters to test it than to write the question. ;)
$ clj -Sdeps '{:deps {org.clojure/clojure {:mvn/version "1.12.0-alpha7"}}}'
Clojure 1.12.0-alpha7
user=> (map String/toUpperCase ["hi" "there"])
Syntax error (IllegalArgumentException) compiling at (REPL:1:1).
Multiple matches for method toUpperCase in class java.lang.String, use param-tags to specify
Haha maybe for you
I did try with cider, to be fair, but was getting an error in my scratchpad environment - I think 1.12.0-alpha6 had an issue somewhere
Clojure 1.12 feedback (https://ask.clojure.org/index.php/13710/method-values-and-syntax-errors): Someone on reddit brought up the point that by treating ambiguous method values as syntax errors, the code is now more brittle than using the old interop syntax because in Java, adding an overload (by arity or by type) is a non-breaking change. This can lead to forcing one to use a specific version of a Java library merely to continue using a Clojure library that uses the new method values syntax, because later versions of the Java library provide overloads that make the Clojure code ambiguous where it previously was not. Given Clojure's historically lenient and "trust the programmer" stance on interop (`(.foo {})` compiles just fine even tho {}
is a literal of known type), it seems odd that this new syntax throws syntax errors instead of falling back to reflection (with an associated reflection warning).
Just to make sure I get it, you're saying you prefer that one be forced to do:
(map ^[] String/length ["abc" "abcd"])
And not allow:
(map String/length ["abc" "abcd"])
because the latter would break if another method overload was added to String/length?Yes, this is a new syntax that makes different choices. With param-tags, you can choose the specificity you want depending on whether you think this is important. Because param-tags specify arity, adding new arities is NOT a breaking change. Adding a new overload in a parameter that was previously specified by a wildcard would be, and you can avoid that by being more specific in your param-tags.
I personally am wondering about the decision to not fall back to reflection when given ambiguous method values, given Clojure's existing allowance and reliance on it. Feels sharply incongruous
many people doing production Clojure work very hard to avoid reflection and will find the guarantee of no inference to be a welcome option
The original poster is worried about people using no param-tags or wildcard param tags when it's unambiguous and then the underlying java library adding overloads which make it ambiguous and causing syntax errors when previously there were none
well, I think there are many people that welcome an error over reflection in this case
Sure, I understand the desire to not have any inference, I also work hard to avoid it. I don't know that I see the value in a hard error vs emitting a reflection warning, when it can affect downstream users
You can opt out in your own code by not using it. You cannot opt out in libraries that use it.
also, if the situation arises in a library, now you don't have a error but a new reflection warning you "can't fix", but at least it is not an error
but if you require no reflection it IS an error
if a library uses param-tags and specifies types, then they will never encounter this situation, so maybe that's a best practice for lib authors
but I think it is very easy to forget, because when you are developing your lib, if there is only one option, you just call the thing with the shortest syntax
Maybe removing wildcard/requiring a param-tag would be helpful here. Enforce that it has to be explicit, cannot be ambiguous under any circumstances. That would solve the issue of a java library adding an overload and breaking the clojure code
seems like a good job for a linter. I don't think it makes sense for clojure to require that
but if the trade offs are possibly breaking vs possibly reflecting, isn't the second one less harmful?
for many people, no, I don't think so
imho people interested in avoiding reflections are already using the warn-on-relection flag, so they are going to see it also
that's a different level of "see"
taking something fast and making it slow is a breaking change in many scenarios
you mean from accidental reliance on timing?
no I mean from production requirements
I agree, but it is a scenario that will not be invisible for the reflection interested people (I consider myself one of those)
we compile against a specific JDK release, and will never be broken by new overloads because the method selection is only a compile time thing
but I think this was more about upgrading java libraries
Sounds to me like worrying over a thing that hasn't happened yet and wouldn't happen too often. With clear ways to prevent it (spelling out full signature instead of wildcards).
I think it's right to worry about things that haven't happened but could during alphas. Now's the time to change them if they would be changed
I would challenge the premise that third-party Clojure libraries are exemplary at being future-proof. Stuff occasionally breaks on upgrades, for various reasons, it's not a once-in-a-lifetime event. I'm not sure this hypothetical issue makes the situation much worse in terms of overall reliability.
that's true
as I see it, the argument is not about making libraries future-proof, more about this decision of throwing instead of reflecting in this particular case, while I think Clojure so far doesn't break if it can reflect, and then we have the warn-on-reflection tool for when we are interested in perf.
after reading the convo, i think the original reddit post's position is overstated. It's something to be careful of but not nearly as dire as they say. (only applies if no param-tag or wildcard param-tag) i personally would prefer it to issue a reflection warning instead of a syntax error but it is what it is
It could be so that wildcard hint would become a bad practice to use in libraries (same as leaving out significant type hints now). It is bad either way, reflection or compile-time error.
I agree, practically it will not happen very frequently, and when it does happen, even in libraries, there are escape hatches since most libraries are open source, we can always fork, etc. It just feels weird, since for us interested in avoiding reflections, we will still have to look at all reflections warnings anyway, so that throwing will not help a bit. Also I guess this alphas are the time for bringing all this questions.
I haven't read the r/Clojure post (yet) but my question would be "Who is updating the Java library/JDK version?" -- It can't be the library author because they'd see the breakage and fix it so it must be the user of the library and, yes, if you update a Java library that one of your dependencies relies on and it changes anything, you might break the library. It's pretty common in the Java world for even a patch release update to break downstream code. So I think the concern is way overblown. Now, that said, we are very aggressive about updating dependencies at work so we are "likely" to run into this theoretical... and I definitely want code to "break" (fail to compile) rather than silently switch to a slower implementation (or, worse, accidentally call a different overload that now happens to match better than the old code).
(we have a reflection check as part of CI and fail the build if either new reflection warnings appear or a new file doesn't have (set! *warn-on-reflection* true)
in it)
Oh, and it turns out I have read that post and already replied yesterday that I'd prefer an error to silent reflection 🙂
I've responded similarly to the Ask post -- because I think it's really important to question why you'd be updating a Java library that one of your dependencies uses. That's often going to be a breaking change in some way or other already...
it doesn't need to be you explicitly updating a java library, it could be a transitive dependency
so, it is possible that if you depend on A and B, you upgrade A and then B breaks if they both depend on the same Java lib but A has moved to a newer version
As I said on Reddit, if you update a dependency and Java libraries are involved somewhere, breakage is already fairly common, often in subtle ways...
but shouldn't because of overloading
This is already a problem with large Clojure codebases that have a large number of (transitive) Java dependencies.
Would it be wrong to say that this concern is only about future regressions once this syntax is in the wild? At the moment all of this sort of interop is reflective as the method is not selected at all? It also seems like the changes we are discussing are accretive changes to java libraries. They are providing more by adding overloads and in those situations code might either break with a syntax error, or break by slowing down / using reflection. But we (in the cases being targeted) would have been using reflection anyway prior to 1.12 so is reverting to using reflection so bad?
The change to Thread/sleep
was a breaking change for us -- because CI fails the build if new reflection warnings appear.
"We" (at work) would not have been using reflection so, yes, removing the "no reflection" guarantee from the new syntax is bad as far as we are concerned.
Is what you are saying @U04V70XH6 is that because the reflection warning is only in CI you'd catch the error earlier as a syntax error? And that's preferable?
let's keep in mind the scenario at play here: you load code that refers to a java method by using param-tags with a wildcard you load the same code against a different version of the Java library where the given param tags (with wildcard) is no longer perfectly selective. Compiler rejects ambiguity.
*is omitting the param tags in unambiguous cases also a wildcard?
it's not a wild card but it presumes one arity
There is no change to anything that exists today*. :param-tags are new railroad tracks, no changes to existing tracks. (*) there is a subtlety around static methods in invoke position
I think the time we've spent in this thread is about the same amount of time it will take to resolve all the cases that happen in the next 5 yrs or so
the jdk has had changes like this that affected us I think 3 times in the last decade
I definitely trust a your process and appreciate the care the team put in and sounds like you've thought about this and are confident about the change
I understand the concerns, I appreciate them being raised, I don't think the magnitude of problem is proportional to the concern expressed, but time will tell.
there is nothing preventing us from further refinement in the future
again, imho the original discussion wasn't about this situations being hard to fix (could be if the lib in question is closed source, which is rare) but about why the decision of not falling back to reflection when all interop so far does
(interop doesn't fall back to reflection. The compiler makes a one-time decision when compiling: it found exactly one method or it didn't. Once code is compiled, that decision is never revisited.)
inference and reflection played a prominent role in the early days of Clojure because there were other Lisp/Scheme/Clojure variants and a premium value on concision in that space at the time. in the intervening years, people have built a lot of commercial products and services, and something we see frequently are requests for ways to prevent and avoid reflection (this has mostly gone into linters and tools). being more specific means being more verbose, but the benefit is performance and reduced ambiguity. these are points on a spectrum. how will it all play out in the ecosystem? hard to say without more experience.
> (interop doesn't fall back to reflection. The compiler makes a one-time decision I know, but only for this case, in that one time decision, it doesn't consider reflection
My 2c, the incongruous
remark is fair, but introducing new syntax seems a good opportunity to make said syntax as strict as possible without breaking anything else (as it's new)
Getting an error at compile-time is practically the best level of feedback one can get - it's immediate and official.
Contrariwise, offloading errors to a linter doesn't always work (clj-kondo doesn't currently warn for specific reflection warnings, and if Eastwood does it's only because it wraps Clojure's compiler - was a lucky strike)
I'm reminded of https://ask.clojure.org/index.php/13339/tools-better-detect-errors-coming-existing-methods-fields that was about this very thing, lol. It seems the Uniform Class/member syntax has provided a solution to what both @U45T93RA6 and I were requesting
@U064X3EF3 I'm under the impression that the advice was don't type hint and leave it to reflection unless you have measure and that is causing a perf problem. Not because of verbosity but because of flexibility. Out of curiosity, has yours or others opinion on this changed from experience? I see this being interesting from a library perspective where we don't know the context our code will be running in.
many production users of Clojure just endeavor to remove all use of reflection
if you have millions of lines of Clojure code, case by case is harder than blanket policy, the scale matters
(we're at 140K+ lines -- not "millions" yet but large enough that this sort of thing matters to us)
I see reflection as a convenient devtime tool, it's good not having to hint immediately as you write out a function. But there's little reason to keep reflective invocations after you're done.
Lots of good points in the thread, thank you everyone. I share the worry that being too liberal with the new syntax might result in brittle 3rd party libs. (similarly to how some other constructs like https://ask.clojure.org/index.php/10383/using-proxy-might-make-otherwise-additive-change-breaking are brittle right now - although there's little the dev needing to use proxy can do here) If I may add my 2 cents, my preferences would be: • do not fall back to reflection • always require param tags • do not allow wildcards in param tags
I had the same thought, @U08BJGV6E, so i added a new rule to my linter https://github.com/NoahTheDuke/splint to help enforce that.
Clojure has been known for graceful longevity of programs. Write once, run forever. Clojure is known for what R.Hickey called "situated software", which motivated JVM hosting but also embraces orgs with an IT department that might demand certain updates at certain times. Those new arities will appear at times not chosen by the program's attendants. Compile-time in Clojure may be every time the program runs. For every 100k-line codebase, attended by a staff that can attend to reflection warnings, there may be thousands of little programs that will simply stop working. If Clojure is to offer a concise, easy syntax and a tedious more-precise syntax, then the easy syntax should be forgiving (via reflection) to preserve Clojure's good reputation for graceful longevity. If someone cares enough about avoiding reflection to use the tedious syntax, then they can do that.
They will not suddenly stop working without bumping at least some of the dependencies. The level of dramatic exaggeration in this thread is mind-blowing:). Java libraries (and Clojure libraries too) often introduce breaking changes, so you will have to check and fix things when you upgrade dependencies anyway.
My general worry is not about code I/my team need to update should some transitive dependency get bumped. It's that one clojure lib in the middle that no longer receives updates, because, well, it's done. And yes I know there are such breakages currently, it's just that there's a chance now to not introduce another possible breaking point. Or at least make it somewhat less likely to suffer a breakage, by enforcing a bit of discipline.
> Java libraries ... often introduce breaking changes, At issue are changes that are not even regarded as breaking. A new arity or overload is purely additive in the explicitly-typed realm. Elsewhere in Clojure, the easy syntax is resilient & possibly slow & can display a warning when reverting to reflection. That would be nice here too. The high voltage, "death before reflection" 🙂 contingent is able to look out for itself.
Java libs introduce breaking changes all the time (more frequently than they introduce overloads which are already sometimes breaking). The fear in this thread is not proportional to the effective change. Novelty happens in the world, when it breaks stuff, we fix it - that is true both before and after. This new syntax is not a radical change in this area. If this is judged to be a problem after months or years of experience, we have many options open to address.
Case in point is DXML-66, a still-open reflection issue in clojure.data.xml caused by *Java 10'*s addition of an overload to http://java.net.URLDecoder. Also Pedestal issue 689, for the same issue: closed more than a year after being reported. Stepping back to reflection has afforded data.xml and Pedestal maintainers a lot of grace to deal with these issues. Of course, those cases of reflection could have been avoided pre-emptively by type-hinting every little thing. As will be possible with Clojure 1.12's new syntax also. But history shows we take the idiomatic path and perfection throughout a multi-part system is very difficult to achieve.
@U0HG4EHMH Most library maintainers will prioritize bugs that are actually stopping people doing work over fixing a warning (when the code still works). So, if those library maintainers decide to start using the 1.12 syntax and if an updated Java version or library happens to introduce a change that breaks the library, they'll be a lot more likely to fix it than dealing with "harmless" reflection warnings 🙂 (In other words, I doubt this will actually turn out to be a problem in the real world in future)
And I don't recall anyone pointing this out but... ...library maintainers are not going to adopt 1.12-only syntax if they plan to support 1.11 or earlier... ...so this isn't even likely to become a real problem until libraries drop support for Clojure 1.11. Which might take years.
(I haven't even looked at next.jdbc
to see whether there's any code that might benefit from 1.12 -- because I still support 1.10 and have no plans to drop that any time soon)
State of Clojure Survey 2023 -- Clojure versions still in use (a year ago):
Also consider that post JEP261 the reflection is no longer as "harmless" and can cause breakages just as well.
@U06PNK4HG Could you elaborate on that? JEP 261 is the module system stuff, right?
Yes, which also introduced the concept of illegal cross-module access which was finally shut in JDK17. ... OK, I now see that I've said something stupid. Reflection in such case would be an issue only if you try to invoke a private method, and if you try to do that, then you already have a problem regardless whether there is one matching arity or many.
hey all, I just wanted to follow up and say that we appreciate all the feedback on this and it's given us some new ideas and we'll incorporate into the next iteration, thanks
I have a data structure which contains already "byte array", and wwant to use nippy to thaw/freeze it. But I get this.
[:nippy/unthawable {:type :serializable, :cause :quarantined, :class-name "[I", :content #object["[B" 0x2b04bb91 "[B@2b04bb91"]}]
Check out the docs of *thaw-serializable-allowlist*
:
> If thaw
encounters a disallowed Serializable
class, it will [...] return a safely quarantined object of form
> {:nippy/unthawable {:class-name <> :content <quarantined-ba>}}
.
> - Quarantined objects may be manually unquarantined with
> read-quarantined-serializable-object-unsafe!
.
>
> There are 2x allowlists:
> - *freeze-serializable-allowlist*
; Checked when freezing
> - *thaw-serializable-allowlist*
; Checked when thawing
>
> Example allowlist values:
> - (fn allow-class? [class-name] true)
; Arbitrary predicate fn
> - #{\"java.lang.Throwable\", \"clojure.lang.*\"}
; Set of class-names
> - \"allow-and-record\"
; Special value, see [2]
I saw this. But this is a "Byte array", does this matter ? Not a class
https://clojuredocs.org/clojure.core/require makes no mention of :exclude
but this seems to work as it appears:
(require '[clojure.set :refer :all :exclude [join rename]])
to mimic alex: make an Ask about it?
not familiar
https://ask.clojure.org/index.php/13714/does-exclude-need-added-tohttps-clojuredocs-clojure-require
But it's something you can use in (require ...)
which I found out by accident (and expectation).
So do you expect :only
and :rename
to be included in require
's docstring too? I'm not saying I necessarily disagree with you, btw. But it would seem to imply you expect all of refer's params to be listed
:all
falls in the same category and is mentioned.
Either that or the docstring should mention that all of refer's keywords are honored
not a big deal though...
Yeah, I mean it's a pretty massive docstring, so I can't imagine anyone left it out purely for brevity. Seems preferable to mention it honors all keyword args rather than duplicating the mention of each individually.
I’m getting an error when attempting to start 1.12.0-alpha6 or 1.12.0-alpha7 at datomic.promise$settable_future$reify__12002.get(promise.clj:45) at clojure.core$deref_future.invokeStatic(core.clj:2317) at clojure.core$deref.invokeStatic(core.clj:2336) at clojure.core$deref.invoke(core.clj:2323) This block is repeated multiple times until startup fails. Not sure where to report things like this?
This was reported in another thread a few days ago... let me find the link...
I’ve already fixed it internally but it will probably be rolled back / changed in alpha8
haven't looked at this one yet, we will at least roll it back