2017-07-11 2 views
2

In Python ich in der Lage bin, etwas zu tun wie:Clojure optional Definitionen

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 

Ist es möglich, die gleiche Sache in Clojure zu tun? Ich habe nächste versucht, aber es funktioniert nicht (zum Beispiel Import ist noch erforderlich):

(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 wirft IllegalArgumentException: unable to resolve classname: LongAdder auch wenn LongAdder selbst nicht verfügbar ist.

+0

Also starten Sie ein repl, und Sie geben '(importieren 'java.util.concurrent.NotAClass)' und Sie erhalten eine 'IllegalArgumentException'? Ihr Code funktioniert wie erwartet für mich. Die 'ClassNotFoundException' wird abgefangen. – Josh

+0

Mit einem anderen Hinweis wäre Ihre 'swap!' Form besser ausgedrückt als: '(reset! Long-adder-available true)', und Sie können auch Ihre 'when' prägnanter ausdrücken als:' (wenn @ long-adder -verfügbar ...) ', und sowohl unter dem' try' als auch dem 'when' brauchen Sie kein' do' – Josh

+0

@Josh Ich stimme Ihrer Einschätzung nicht zu, dass es wie vorgesehen funktioniert. Wichtig ist nicht die ClassNotFoundException, die zur Laufzeit vom 'import'-Ausdruck ausgelöst wird, sondern die zur Kompilierzeit vom Code innerhalb des' when' ausgelöst wird, der kompiliert wird, ob er tatsächlich ausgeführt wird oder nicht. – amalloy

Antwort

1

Wie von @amalloy in den Kommentaren hingewiesen, kompiliert der Code innerhalb when nicht. Ich bin mir nicht sicher, ob es eine Möglichkeit gibt, den Code neu zu schreiben, damit er kompiliert wird. Es ist jedoch möglich, eine Kompilierung zu vermeiden. Clojure-Makros können verwendet werden, um Code von der Kompilierung auszuschließen.

Ein Makro kann den Import einer Klasse versuchen und nur wenn dies gelingt, den Code mit der Klasse ausgeben. Es gibt einfachere Möglichkeiten, um zu überprüfen, ob eine Klasse im Klassenpfad vorhanden ist, aber es ist wichtig, zur Kompilierungszeit import aufzurufen. Auf diese Weise kann der Code einfache Klassennamen verwenden (wie LongAdder).

Bei diesem Problem könnte die Lösung wie das folgende Beispiel aussehen. Das Stück Code calling import ist ein bisschen hässlich mit eval etc., aber es ist schwierig, nicht-literale Argumente an import übergeben. Wenn dieser Code nicht generisch sein muss, kann der Klassenname fest codiert sein und einige andere Dinge können vereinfacht werden.

(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))) 

Ich sollte erwähnen, dass die Antwort stark von Jay Fields' Blog-Eintrag "Clojure: Conditionally Importing" und this answer inspiriert ist.

+0

Ich dachte über ein Makro nach, aber ich dachte, es gibt einen besseren Weg. Vielen Dank. – gagoman