This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-10-13
Channels
- # ai (5)
- # announcements (4)
- # babashka (34)
- # beginners (78)
- # biff (6)
- # calva (41)
- # cider (47)
- # clerk (1)
- # clj-commons (3)
- # clj-http (1)
- # clojure (72)
- # clojure-europe (51)
- # clojure-nl (1)
- # clojure-norway (87)
- # clojure-romania (1)
- # clojure-uk (5)
- # clojuredesign-podcast (2)
- # community-development (1)
- # conjure (2)
- # cursive (11)
- # datomic (6)
- # docker (4)
- # emacs (13)
- # exercism (20)
- # hyperfiddle (56)
- # matrix (6)
- # membrane (4)
- # nbb (11)
- # off-topic (88)
- # pathom (7)
- # pedestal (1)
- # polylith (20)
- # portal (16)
- # practicalli (1)
- # re-frame (13)
- # reagent (4)
- # reitit (2)
- # remote-jobs (7)
- # shadow-cljs (49)
- # sql (4)
I'd like to write a code that will, conditionally at runtime, call (datadog.trace.api.CorrelationIdentifier/getTraceId)
only if (datadog.trace.api.CorrelationIdentifier)
is available (injected via commandline).
Something like:
(defn load-datadog []
(try
(.importClass (the-ns *ns*)
(clojure.lang.RT/classForName "datadog.trace.api.CorrelationIdentifier"))
(timbre/info "DataDog available")
(alter-var-root #'get-context
(fn []
{:dd.trace_id (datadog.trace.api.CorrelationIdentifier/getTraceId)
:dd.span_id (datadog.trace.api.CorrelationIdentifier/getSpanId)}))
(alter-var-root #'available? (constantly true))
(catch ClassNotFoundException e
(timbre/info "DataDog unavailable"))))
This of course fails since at compile time datadog.trace.api.CorrelationIdentifier/getTraceId
is unavailableIs this possible to do something similar to classForName
but for static method instead?
A common way to do it is move the conditional logic into a macro. Then that macro can conditionally return a syntax-quoted form that will be fed into the compiler only if the class is there. BTW instead of posting multiple messages one after the other, it's better to just edit the first one.
Macros are going to be resolved at compile-time, and I need to deploy this as a precompiled JAR.
Well, no need to remove and edit now, after I have answered. :) But not a big deal. It's just that with a single message it's fewer notifications to those who have them enabled, and it's easier to link to older threads.
You could maybe use eval
like this:
(try
(eval '(java.util.concurrent.Executors/newVirtualThreadPerTaskExecutor))
(catch clojure.lang.Compiler$CompilerException _
:fallback))
Is it possible for Clojure core caches to invoke a client specified function when an entry is evicted?
evict
is a protocol method of a cache factory. You can write a delegating implementation that will call anything you want. But there's nothing built-in, AFAICT.
Added pomegranate as a dependency in my :user profile (envying CL with quickload), and it caused an issue with next.jdbc because pomegranate used an old version of slf4j. Is there a better practice than this, is it documented somewhere?
FWIW, you can do it in 1.12.0-alpha2
without any extra deps: https://clojure.org/news/2023/04/14/clojure-1-12-alpha2#_add_libraries_for_interactive_use
Clojure 1.12.0-alpha4 is the latest version (with an important bug fix for drop
).
Even before 1.12, I used tools.deps.alpha
as a source (git) dep and leveraged the code in that to add dependencies to my running REPL.
Also, next.jdbc
itself does not depend on slf4j so that conflict is coming from some other dependency in your project:
> clojure -Sdeps '{:deps {com.github.seancorfield/next.jdbc {:mvn/version "RELEASE"}}}' -Stree
Downloading: com/github/seancorfield/next.jdbc/maven-metadata.xml from clojars
org.clojure/clojure 1.12.0-alpha4
. org.clojure/spec.alpha 0.3.218
. org.clojure/core.specs.alpha 0.2.62
com.github.seancorfield/next.jdbc 1.3.894
. org.clojure/java.data 1.0.95
. org.clojure/tools.logging 1.2.1
. camel-snake-kebab/camel-snake-kebab 0.4.3
Very strange. First time I used mvn deps:tree and found it does depend on that, now I used it again and it doesn't anymore. I only excluded slf4j with :exclusions
(in pomegranate's dependency list) and now slf4j isn't anywhere to be found. Also, the library is buggy anyway, with stray warnings and exceptions. Are logging libraries really that complicated?
Ah, I understand. Simple is in the name, after all.
Has anyone used SwiftUI for frontend and Clojure for backend in the same project? Curious if there are any examples, footguns, if itโs awesome, etc.
Yes, a client of mine is creating a client for the CMS that I've written in Clojure. All closed-source though, so no examples. Can't say it's awesome because I strongly dislike Swift and the current state of its ecosystem. You'll also have to use JSON or write your own Transit library (even if my client opens the source of the Swift library for Transit they've written, it's far from being complete).
Not SwiftUI, but you might look at ClojureDart for iOS and MacOS apps; I'm using it, and it's great! The folks who wrote it are super responsive in #C03A6GE8D32 too.
This surprised me, but I'm not sure if my expectations were just wrong...
We've been using JDK 20 everywhere for a while and I decided to start testing on JDK 21. I built an AOT'd uberjar (from unchanged code -- only the JDK was updated), and the JAR will not run on JDK 20 because there are references to SequencedCollection
in the bytecode... which is new in JDK 21.
Should I have just expected that to break?
Generally, yes
I guess I've just been lucky in the past, being somewhat cavalier about the JDK used for build and the JDK used for run. I can't repro a simple case of this locally right now but... lesson learned ๐
You can compile .java to .class with a newer JDK (than the one that will run it) by using the --release flag with javac (or so I read) ... is there a comparable technique to use with Clojure?
Depending on how the uberjar is constructed it may be collapsing a, uh, what is it called a multi-release jar
Where different classes are exposed to different versions of java, and building the uberjar on java version whatever is only including the classes for that version
This is using the "default" uber approach in tools.build
with no special Java or Clojure compiler options.
I have seen this as well, I am trying to figure out the cause I am guessing it's because my build is using an unpinned container ie clojure:tools-deps where as the compose file is using eclipse-temurin:17 code wise nothing really changed it just topped working, going to try and pin the build to jdk 17 and see if that helps
Ah, this is breaking Jepsen all over the place too. Has anyone worked out a workaround? https://github.com/jepsen-io/jepsen/issues/585
In general, compile with the lowest Java you want to support or use the โrelease flag when compiling
Huh, even with :javac-options ["--release" "11"]
in Jepsen's project.clj
, projects that depend on Jepsen as a library explode on any JDKs below 21. ๐
https://clojurians.slack.com/archives/C03S1KBA2/p1697234118933419 has some more debugging info related to this thread (no solution, just more zeroed in on the issue)
Yeah, for me it explodes because of Clojure's built-in rrb_vector.clj
https://github.com/jepsen-io/jepsen/issues/585
if you are aot jepsen with a lower jdk it should fix it, but it isn't a javac/java lang issue, so passing flags to javac won't fix it
the clojure compiler is what ends up emitting references to the java.util.SequencedCollection class (something about reifys and default methods, maybe triggered by the introduction of the new interface in the ancestors of List) and the clojure compiler looks at what classes are live and available when it is compiling, no --release or --target or whatever flags for it
Ahhh, right then. I should write up a blog post, it's been impossible to find resources for this.
Right, maybe this will help someone else later: https://aphyr.com/posts/369-classnotfoundexception-java-util-sequencedcollection
Would you (or someone who knows what this is about) recommend a clarification to https://clojure.org/reference/compilation ? I do not see Java version compatibility addressed.
@U038RGYDGUR Your post sounds like you think the Clojure compiler would get a "fix" for this -- but it seems like this is the expected behavior (compile on JDK N, code may require JDK N+ to run). @U064X3EF3 Is a change to the Clojure compiler being considered for this JDK 21-specific issue?
Oh, maybe I misunderstood! I've been compiling on newer JDKs for deployment on older JDKs for a decade using -source
and -target
, just kinda figured that was normal. I build my libraries on my desktop which runs JDK 21, so I suspect I'm gonna mess this up routinely for the next N years.
There are multiple things here - bytecode version and compiled JDK references. The Clojure compiler for several releases has emitted Java 8 bytecode, regardless of which Java version you are compiling Clojure with (via the embedded ASM), and thus we support Java 8+ at runtime. If you are compiling interop invocations into the JDK, you are implicitly bound to whatever you're using - you can't compile an interop call to a JDK 21 only method and expect it to work at runtime on JDK 17. The subtlety here is that if you use deftype and implement List, you'll have an implicit dependency on their new parent interface SequencedCollection, which has a new method reversed() which comes into the bytecode of the deftype. We had a similar kind of problem when Java added the default overloaded Collection.toArray() in Java 11.
Would it make sense to change rrb_vector.clj?
possibly. that's roughly how we "fixed" this in this place and others last time
(Clojure primitive vectors in gvec have the same issue)
for the toArray case, that was a new method in an existing type extended in a deftype so it could be implemented and then just ignored on older jvms (I think). for this, it's a new method on a new type and that affects the return type of the method emitted into the deftype class (it returns SequencedCollection) so you can't prevent it from getting into the emitted deftype class
in any case, I will take a closer look at this and see if there is something that can be done in the deftype emitter
Thanks for thinkin bout it, Alex. ๐
logged at https://ask.clojure.org/index.php/13748/java-lang-noclassdeffounderror-java-sequencedcollection
@seancorfield can you pinpoint what methods the bytecode invokes on SequencedCollection, and what Clojure form made that call?
It's a new interface inserted into the hierarchy of List, Deque, LinkedHashMap (and a few other things) so I guess it's pretty easy to run into... https://docs.oracle.com/en/java/javase/21/core/creating-sequenced-collections-sets-and-maps.html
I guess anything that makes instances any of those leaf classes is going to require the new interface is present...
> JAVA_HOME=$OPENJDK21_HOME clj
Clojure 1.12.0-alpha4
user=> (System/getProperty "java.version")
"21"
user=> (ancestors java.util.List)
#{java.util.SequencedCollection java.lang.Iterable java.util.Collection}
user=>
So anything that touches java.util.List
is going to need it?
My actual failure was from a deftype
in instaparse
which implements java.util.List
Exception in thread "main" java.lang.NoClassDefFoundError: java/util/SequencedCollection
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:495)
at java.base/java.lang.Class.forName(Class.java:474)
at clojure.lang.RT.classForName(RT.java:2209)
at clojure.lang.RT.classForName(RT.java:2218)
at instaparse.auto_flatten_seq$fn__987.<clinit>(auto_flatten_seq.clj:314)
https://github.com/Engelberg/instaparse/blob/master/src/instaparse/auto_flatten_seq.cljc#L314 with the reference to java.util.List
here https://github.com/Engelberg/instaparse/blob/master/src/instaparse/auto_flatten_seq.cljc#L410@U050ECB92 This breaks if compiled on JDK 21 and run on JDK 20:
(let [a (reify java.util.List
(get [this x] 42))]
(println (.get a 0)))
the reify bytecode contains two things, a get(x) and an override for the default method: public java.util.SequencedCollection reversed(); Code: 0: aload_0 1: invokeinterface #39, 1 // InterfaceMethod java/util/List.reversed:()Ljava/util/List; 6: areturn
Ah, so that's why my first attempt to repro (messing with ArrayList
instances) wasn't enough -- but the reify
was.
I'm guessing quite a bit of Clojure code out there implements at least one of those interfaces that now requires SequencedCollection
. Good to know.
FWIW I ran into exactly the same issue this week
yes I'm just trying to get an impression if there is a frequent path that encounters it
I'll do a bit more testing once I've cleaned up my current "mess"...