Search code examples
clojure

Clojure optional definitions


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.


Solution

  • 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.