gen-class/AOT considered harmful (in libraries)???

669 views
Skip to first unread message

dimitris

unread,
Aug 22, 2019, 12:59:44 PM8/22/19
to Clojure

Hi folks,

This is a genuine question that has tripped me quite a few times, so I'd appreciate some closure - pun intended ;)

Suppose you are developing a library foo. In the absence of AOT, you don't need to include Clojure as an actual dependency (perhaps as a :dev one so that you can work away), because the caller/user of your library is expected to provide the Clojure version he/she wants (ignoring features introduced in some particular release and such). All good so far...

Now suppose said library uses a `:gen-class` ns (because it needs to register a service through java-SPI and `proxy` won't cut it), and that you 've managed to restrain AOT only to that specific ns (through runtime `requiring-resolve` calls). Does that automatically make that library only compatible with whatever Clojure version you compiled it with (which would mean that Clojure now needs to be a proper dependency for things to work)?


Many thanks in advance...

Kind regards,

Dimitris



Matching Socks

unread,
Aug 22, 2019, 9:00:16 PM8/22/19
to Clojure
You are considering gen-class as an alternative to writing the service-provider class in Java and using only methods in Clojure's public Java API to connect it with an implementation-in-Clojure?

dimitris

unread,
Aug 23, 2019, 3:15:35 AM8/23/19
to clo...@googlegroups.com

Why would I write the class in Java, when this below works exactly as expected?

(ns foo
  (:gen-class :name foo.Bar
              :extends java.lang.System$LoggerFinder
              :constructors {[] []}))

(let [slm (delay
            (-> 'foo/system-logger-memo
                requiring-resolve
                var-get))]
  (defn -getLogger
    "Returns a subclass of `System$Logger` which
     routes logging requests to the `core/*root*` logger."
    [this name module]
    ;; resolve it at runtime (and only once),
    ;; in order to prevent AOT leaking out of this ns
    (@slm)))


My question boils down to this:

Let's say that I used clojure1.10 to compile the above gen-class. Does the project containing it need to specify Clojure 1.10 as a proper dependency, or will the end user be able to provide any version of Clojure greater or equal to 1.10?

Thanks in advance...

Kind regards,

Dimitris


On 23/08/2019 02:00, Matching Socks wrote:
You are considering gen-class as an alternative to writing the service-provider class in Java and using only methods in Clojure's public Java API to connect it with an implementation-in-Clojure?
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/clojure/0660b640-cf91-429b-9a40-1695e7955aef%40googlegroups.com.

dimitris

unread,
Aug 23, 2019, 3:18:35 PM8/23/19
to clo...@googlegroups.com

Ok, so after a bit of more thinking/reading, I realise now that my question was somewhat misplaced.

Any AOT compiled code (not just gen-class), is an artifact of the compiler used to compile it, and therefore, in some sense, implicitly tied to that compiler version. However, that's not to say that the produced class definitely will or won't work with some other version. If my understanding is correct, it will work for future versions (which is the main point here) as long as Clojure itself maintains backwards compatibility (in this case wrt to gen-class).    

I'm still torn on whether to actually add Clojure as a proper dependency - I think I will probably end up doing so, but I'm not thrilled about it...

Kind regards,

Dimitris

Didier

unread,
Aug 31, 2019, 5:39:18 AM8/31/19
to Clojure
Clojure does not guarantee binary compatibility between versions, but almost always happens to be compatible. Yet, it's best not to distribute your Clojure libs as compiled classes, much better to do so as source.

For a gen-class though, it works like Java. Java guarantees backward compatibility of classes, as far as I know. So you should be good as long as you make sure that none of the Clojure code gets AOTed and included in the jar by accident as you compiled the gen-class.

Like keep your gen-class in their own namespace with only the -xyx fns in it. And have those instantly delegate to a call to another namespace.

Or what I do is I AOT, but then when I package the Jar, I only include the gen-class .class file in it. I hand pick it for inclusion in the Jar, and don't include any of the other .class files.

Matching Socks

unread,
Aug 31, 2019, 5:58:13 AM8/31/19
to Clojure

On Saturday, August 31, 2019 at 5:39:18 AM UTC-4, Didier wrote:

with only the -xyx fns in it. And have those instantly delegate to a call to another namespace.


The gen-class :impl-ns option is useful here.  It achieves the delegation without spreading AOT to the implementing namespace.

Spenser Truex

unread,
Sep 1, 2019, 12:29:18 AM9/1/19
to Clojure
I'm not sure if it is harmful.

atdixon

unread,
Sep 1, 2019, 8:08:22 PM9/1/19
to Clojure
Hi, Dimitris - 

It looks from Clojure source [1] that Clojure compiles to v1.8 class files, so this should be mean you can run in any JVM 1.8 and beyond.

When you say you are deliberating including Clojure as a proper dependency. I've noticed that some Clojure libraries will have Clojure listed as a dependency in their repository POM but it will be marked with scope `provided` which is a way of saying this is a dependency that the JAR is expected to be provided by the library consumer at runtime.

I'm not sure why AOT changes the question of whether to include the Clojure jar as a dependency; I think you have this question with non-precompiled/AOT'd libraries, no? I do understand that with AOT your lib will have *.class files in the JAR that directly reference Clojure classes. But in some IDEs -- just having the dependency as provided will suffice to make it available for linking/compiling.


For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clo...@googlegroups.com.

Didier

unread,
Sep 1, 2019, 11:31:46 PM9/1/19
to Clojure
AOT does matter, because AOT is transitive. Effectively, all AOT builds are like Uberjars. They compile your code and the code of your dependencies and theirs as well into .class files putting everything in the build folder. Then the package will take all the classes and put them in the Jar.

Any library that does that does it wrong. Don't AOT your libraries for that reason. Or make sure if you do, you are only including .class of your library code, not any of its dependencies. And even ideally, like I said, only include the bare minimum, so only .classes required for interop. The rest package as source.

atdixon

unread,
Sep 2, 2019, 9:56:50 AM9/2/19
to Clojure
I was responding to this:

> I'm still torn on whether to actually add Clojure as a proper dependency

The question to have Clojure as a proper dependency doesn't seem to change whether you AOT or not. I take "proper dependency" to mean a Maven/Lein dependency that ships with the dependency list in your library's POM.

I agree that you that you shouldn't ship classes AOT'd outside of your library/its namespaces.  

Didier

unread,
Sep 3, 2019, 3:45:28 AM9/3/19
to Clojure
Hum... well, the dependencies normally specify the extent of the AOT, in the sense that the AOT follows your code `require chain`. Which is why say :dev dependencies or test dependencies normally wouldn't be included in your AOT. That said, Clojure is required to AOT, and so it would be on the classpath and it will be an implicit require of any namespace, so it will get AOTed no matter what. I think that reasoning makes sense.

Still, a Clojure library shouldn't include a dependency on Clojure in my opinion, as Clojure will always be provided by the container in those cases, unless it is meant to be consumed from Java. The best is to use the type of dependency that's like: use the parent specified one, unless there is none, then use this one. Can't quite remember all the different dm, but I believe lein, maven, etc. all have something like this.
Reply all
Reply to author
Forward
0 new messages