Yes but it's a big time sink, you waste time thinking on how to wrap library, and for some libs you end hitting some blockers cause Clojure's interop is not that smooth, ending up writing two wrappers, one in Java(to make the lib usable from Clojure without pulling your out) and one in Clojure.
And I'm talking about ad-hoc use of Java libs, writing a more idiomatic wrapper is a lot work. The Clojure ecosystem is pretty much abandonware at this point, even for high profile libraries.
In languages with big ecosystems, you simply just use the lib and get work done, no cognitive overhead, no time sink.
https://clojure.org/reference/java_interop
It's pretty easy to translate things like
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
//...
Instant nowInstant = Instant.now();
Instant fiveMinutesAgo = nowInstant.minus(Duration.ofMinutes(5));
Date nowDate = Date.from(nowInstant);
to (import '(java.time Duration Instant))
(import '(java.util Date))
;; static method call with / (slash)
(def now-instant (Instant/now))
;; instance method call with dot. First arg is the instance
(def five-minutes-ago (.minus now-instant (Duration/ofMinutes 5))
(def now-date (Date/from now-instant))
Implementing interfaces and extending classes are a bit more involved, but I haven't had to do much of that in my experience, and when I have had to, that's been pretty good, too.Here's an example implementing the AWS Lambda RequestStreamHandler interface
https://github.com/aws/aws-lambda-java-libs/blob/master/aws-...
(ns com.example.hn.handler
(:gen-class
:implements [com.amazonaws.services.lambda.runtime.RequestStreamHandler])
(:require
[clojure.java.io :as io]))
;; implement the RequestStreamHandler handleRequest method
(defn -handleRequest
"echoes request"
[_ input-stream output-stream _context]
(let [w (io/writer output-stream)
event (slurp input-stream)]
(-> w
(.write event)
(.flush))))
That's probably shorter than the equivalent Java implementation.One of the more complicated things I've had to do was parseBest as part of DateTimeFormatter, which takes a TemporalQuery as an argument. It has a solution like this:
https://clojurians-log.clojureverse.org/clojure/2019-09-06/1...
I've also worked with JDBC libraries to wrap driver-specific features. JDBC drivers have been around for a long time, and driver-specific stuff can vary in how well it's designed. I've also used jna through Java from Clojure when I was just starting out, which wasn't all that hard for a newbie to the language.
From your comments in other Clojure-related threads we've participated in, it sounds like your experience is much different from mine, but often lacks in specific examples. Without seeing those, it's hard to know what you're describing. If you think Clojure isn't the bees knees, that's perfectly fine. I think readers would benefit from knowing specifics of what you've found frustrating.
If you're using proxy, you pay a large performance penalty, and you can't call protected superclass methods or access protected superclass fields without using reflection.
gen-class is a truly awful thing to use.
Even when type hinted, defrecord fields all have type Object.
primitive type hinting, and corners like the type hinting in deftype/reify (where you have to either type hint everything or nothing)
Having to type hint with ^"[Lcom.example.project.package.Class;" is foul.
No niceties like SAM conversion, No integration with java.util.function interfaces.
There are some things that aren't as easy. And sometimes it's a better choice to drop into Java to provide a shim between the package you're interacting with and Clojure to make the interop easier, or when performance matters.
Regarding hinting for defrecord fields and deftype/reify, do you have references where this has been a problem for you? It's not something I've run into, but that's just my experience.
As for java.util.function interfaces, I just did this within the past couple of weeks, which worked for my use case:
defn ^java.util.function.Function as-function [f]
(reify java.util.function.Function
(apply [this arg] (f arg))))
For SAM conversions in general, there's https://github.com/ajoberstar/ike.cljj . I haven't used it myself, but perhaps the pattern used there would work in you.It sounds like what you've worked on has been relatively performance sensitive, which can definitely a thing. So perhaps some of these interop suggestions won't work for you.
Again, thanks for taking the time to be more explicit about what you've found frustrating.
https://clojure.atlassian.net/jira/software/c/projects/CLJ/i...
A couple that are related to the items 'Scarbutt mentioned:
- https://clojure.atlassian.net/browse/CLJ-2365 Integration with java.util.function interfaces
- https://clojure.atlassian.net/browse/CLJ-1255 Support Abstract Base Classes with Java-only variant of "reify"
- https://clojure.atlassian.net/browse/CLJ-1001 Proxy cannot call proper super-class method
My take on this is that Clojure provides host (Java) interop, not replacement. Part of that is if you're doing really Java-y things, such as extending abstract classes, that's probably better done in Java. These class-based inheritance/extension concepts aren't core to what Clojure is about. Polymorphism and extension is addressed by other means in Clojure.
A comparison: I know little about Kotlin, but I wouldn't be surprised if it has a better story about Java class extension and interface implementation as I think one of its goals is to be in some ways a better Java. I don't think that's a goal that Clojure shares. If those are features you are looking for, Clojure may not be a good fit.