In Python I'm able to do something like:
fast_thing_available = True
try:
import fast_thing
except ImportError:
fast_thing_available = False
# snip
if fast_thing_available:
default = fast_thing
else:
default = slow_thing
Is it possible to do the same thing in Clojure? I've tried next, but it fails (e.g. import is still required):
(ns sample.ns)
(def ^:private long-adder-available (atom false))
(try
(do
(import 'java.util.concurrent.atomic.LongAdder)
(swap! long-adder-available (constantly true)))
(catch ClassNotFoundException e ()))
(when (true? @long-adder-available)
(do
; Here I'm using LongAdder itself
))
Code throws IllegalArgumentException: unable to resolve classname: LongAdder
even if LongAdder
itself is not available.
As pointed out by @amalloy in the comments, the code inside when
does not compile. I am not sure if there is a way to re-write that code so it compiles. However it is possible to avoid compiling it altogether. Clojure macros can be used to exclude code from compilation.
A macro can attempt importing a class and only if that succeeds emit the the code using the class. There are simpler ways to check if a class exists in the classpath, but it is important to call import
at compile time. That way the code can use simple class names (like LongAdder
).
When applied to this problem, the solution might look like the sample below. The piece of code calling import
is a bit ugly with eval
etc. but it is tough to pass non-literal arguments to import
. If there is no need for this code to be generic, the class name can be hard-coded and a few other things can be simplified.
(ns sample.core)
(defmacro defn-if-class
"If clazz is successfully imported, emits (defn name args then)
Emits (defn name args else) otherwise."
[clazz name args then else]
(if (try (eval `(import ~clazz)) true (catch Exception e nil))
`(defn ~name ~args ~then)
`(defn ~name ~args ~else)))
(defn-if-class java.util.concurrent.atomic.LongAdder
foo []
;; if class exists
(doto (LongAdder.)
(. increment)
(. sum))
;; else
"fallback logic in case the class is not in classpath")
(defn -main [& args]
(println (foo)))
I should mention that the answer is heavily inspired by Jay Fields' blog post "Clojure: Conditionally Importing" and this answer.