2016-08-09 165 views
0

Ich möchte einen Clojure-Code ausführen, der von einer bestimmten var abhängt, aber nur, wenn diese Variable definiert ist. Als vereinfachtes Beispiel sollte der Körper der if Form nur dann ausgeführt werden, wenn sym definiert:Wie man ein Symbol nur verwendet, wenn es definiert ist

(if (resolve 'sym) (println sym)) 

Leider ist dies nicht funktioniert. Wenn sym nicht definiert ist, versucht der Compiler immer noch, es zu lösen und wirft:

CompilerException java.lang.RuntimeException: Unable to resolve symbol: sym in this context 

Aus der Lektüre Rich-Hickley Kommentar here, entnahm ich, dass dieses Verhalten zu Clojure der Nutzung von (meist) Single-Pass-Zusammenstellung beruht. Jedoch, so viel Sinn wie das macht, führt es offensichtlich in diesem Fall zu unerwünschtem Verhalten.

Ich kann indem das Symbol Auflösung, um das Problem bekommen zur Laufzeit geschehen:

(if (resolve 'sym) (println (deref (resolve 'sym)))) 

Aber dies ist ein unerwünschter Hack. Gibt es einen besseren Weg, oder ist es mit Clojures Read-Eval-Modell nicht möglich?

(Warum muss ich das tun? Ich in mehrere zusammensetzbare Profile definiert haben meine profiles.clj. Einer von ihnen ist für vinyasa, die mir verschiedene Funktionen in die bequem zugänglich . Namensraum injizieren können. Andere verschiedene andere Dienstprogramm laden Bibliotheken. In den Profilen für die anderen Utility-Bibliotheken, möchte ich Vinyasa verwenden, um die wichtigsten Funktionen zu injizieren, aber nur, wenn Vinyasa ist geladen. ich bin currently using eine Variation des Hack oben.)

Antwort

1

Die Vorgehensweise wird empfohlen ded by @Michiel mit when-let ist der beste Weg, um dieses Problem zu lösen. Wichtig ist, dass Sie die Auflagen unter Verwendung let ‚s Fähigkeit, nahezu vollständig transparent machen eine bestehende Bindung an Schatten:

(when-let [foo (resolve 'foo)] 
    (foo)) 
;;=> nil 

(defn foo [] 
    :bar) 

(foo) 
;;=> :bar 

(when-let [foo (resolve 'foo)] 
    (foo)) 
;;=> :bar 
+1

Das ist sehr interessant! Es ist nicht sofort offensichtlich, was hier vor sich geht, aber wenn man einen Var als Funktion nennt, dereferenziert er sich selbst und ruft dann die Funktion auf, auf die er zeigt. Dies bedeutet insbesondere, dass Sie, wenn Sie diesen Trick für Vars verwenden möchten, die keine Funktionen sind, diese manuell dekompilieren müssen - aber Sie müssen nicht für Vars, die Funktionen sind. –

+1

@RadonRosborough Ja, das stimmt genau. Sie können dies überprüfen, indem Sie den [Var-Quellcode] (https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Var.java#L374-L376) überprüfen. –

1
(defn get-when-var-defined [sym] 
    (when-let [var-for-sym (resolve sym)] 
    @var-for-sym)) 

(get-when-var-defined 'foo) ;;=> nil 
(def foo 1) 
(get-when-var-defined 'foo) ;;=> 1 
+0

Hmmm ... Also, im Grunde das gleiche wie das, was ich habe, aber in eine Funktion abstrahiert? Ich denke, das ist vernünftig genug; Ich werde dies jetzt akzeptieren, wenn keine alternativen Lösungen angeboten werden. –

+1

müssen Sie natürlich keine Funktion verwenden. '(when-let [v (auflösen 'sym)] @v])' ist die einfachste Version dieser –