2017-04-16 2 views
1

In clojure, kann man idiomatisch den Namen einer Funktion in seinem Körper erhalten, hoffentlich so, ohne einen neuen Wrapper für die Definition der Funktion einzuführen? Kann man den Namen der Funktion auch innerhalb des Attributs der Funktion aufrufen?Der Name einer Funktion in ihrem Körper oder: Test Körper

Für die Motivation kann dies für bestimmte Protokollierungssituationen hilfreich sein, sowie dafür, dass der Körper von :test sich nicht um Änderungen des Namens der Funktion kümmert, für die er bereitgestellt wird.

Eine kurze Erläuterung der nächsten, die meta folgt folgt; Es gibt keine this Idee, meta zu liefern, soweit ich weiß, in clojure.

(defn a [] (:name (meta (var a)))) 

Offensichtlich ist es einfach, mit einem Wrapper-Makro zu erreichen.

Edit: zum Glück niemand bisher genannten Lambda-Kombinatoren.

Antwort

2

Es gibt zwei Möglichkeiten, Ihre Frage anzugehen. Ich vermute jedoch, dass Sie, um das, was Sie tun möchten, vollständig zu automatisieren, Ihren eigenen benutzerdefinierten defn Ersatz/Wrapper definieren müssen.


Die erste Sache zu erkennen ist, dass alle Funktionen anonym sind. Wenn wir geben:

(defn hello [] (println "hi")) 

wir wirklich eingeben:

(def hello (fn [] (println "hi")) 

wir ein Symbol hello schaffen, die zu einem anonymen var Punkte, die wiederum auf eine anonyme Funktion. Wir können jedoch die Funktion einen „internen Namen“ wie so geben:

(def hello (fn fn-hello [] (println "hi"))) 

So, jetzt können wir die Funktion von außen über hello oder von innen Zugriff auf entweder hello von fn-hello Symbole (bitte nicht immer Verwenden Sie hello in beiden Standorten oder Sie schaffen eine Menge Verwirrung ... obwohl es legal ist).

ich häufig die fn-hello Methode (sonst) anonyme Funktionen verwenden, da geworfen Ausnahmen das fn-hello Symbol enthalten, die die Quelle des Problems wesentlich erleichtert das Aufspüren (die Zeilennummer des Fehlers fehlt oft aus dem Stack-Trace). Zum Beispiel, wenn Instaparse verwendet, müssen wir eine Karte von anonymen Transformationsfunktionen wie:

{ 
    :identifier  fn-identifier 
    :string   fn-string 
    :integer  (fn fn-integer [arg] [:integer (java.lang.Integer. arg)]) 
    :boolean  (fn fn-boolean [arg] [:boolean (java.lang.Boolean. arg)]) 
    :namespace  (fn fn-namespace [arg] [:namespace arg]) 
    :prefix   (fn fn-prefix [arg] [:prefix arg]) 
    :organization (fn fn-organization [arg] [:organization arg]) 
    :contact  (fn fn-contact [arg] [:contact arg]) 
    :description (fn fn-description [arg] [:description arg]) 
    :presence  (fn fn-presence [arg] [:presence arg]) 
    :revision  (fn fn-revision [& args] (prepend :revision args)) 
    :iso-date  (fn fn-iso-date [& args] [:iso-date (str/join args)]) 
    :reference  (fn fn-reference [arg] [:reference arg]) 
    :identity  (fn fn-identity [& args] (prepend :identity args)) 
    :typedef  (fn fn-typedef [& args] (prepend :typedef args)) 
    :container  (fn fn-container [& args] (prepend :container args)) 
    :rpc    (fn fn-rpc [& args] (prepend :rpc args)) 
    :input   (fn fn-input [& args] (prepend :input args)) 
...<snip>... 
    } 

und gibt jede Funktion die „interne Bezeichnung“ viel macht das Debuggen, viel einfacher. Vielleicht wäre dies unnötig, wenn Clojure bessere Fehlermeldungen hätte, aber das ist ein langjähriger (& bisher nicht erfüllter) Wunsch.

können Sie weitere Informationen finden Sie hier: https://clojure.org/reference/special_forms#fn

Wenn Sie genau lesen, es behauptet, dass (defn foo [x] ...) expandiert in

(def foo (fn foo [x] ...)) 

obwohl Sie experimentieren müssen, um zu sehen, ob dies bereits die Einsatz- gelöst Fall, den Sie suchen. Es funktioniert so oder so wie in diesem Beispiel zu sehen, wo wir die inneren fn-fact Namen explizit vermeiden:

(def fact (fn [x] ; fn-fact omitted here 
      (if (zero? x) 
       1 
       (* x (fact (dec x)))))) 

(fact 4) => 24 

Diese Version funktioniert auch:

(def fact (fn fn-fact [x] 
      (if (zero? x) 
       1 
       (* x (fn-fact (dec x)))))) 

(fact 4)  => 24 
(fn-fact 4) => Unable to resolve symbol: fn-fact 

So sehen wir, dass die „interne Bezeichnung“ fn-fact innen versteckt die Funktion und ist von außen unsichtbar.


Ein zweiter Ansatz, wenn ein Makro verwendet wird, ist die &form globalen Daten zu verwenden, die Zeilennummer aus dem Quellcode zugreifen. In the Tupelo library diese Technik verwendet wird Fehlermeldungen für die

(defmacro dotest [& body] ; #todo README & tests 
    (let [test-name-sym (symbol (str "test-line-" (:line (meta &form))))] 
    `(clojure.test/deftest ~test-name-sym [email protected]))) 

Diese Bequemlichkeit Makro erlaubt die Verwendung von Unit-Tests wie zu verbessern:

(dotest 
    (is (= 3 (inc 2)))) 

, die

auswertet
(deftest test-line-123 ; assuming this is on line 123 in source file 
    (is (= 3 (inc 2)))) 

statt manuell eingeben

(deftest t-addition 
    (is (= 3 (inc 2)))) 

Sie können (:line (meta &form)) und andere Informationen in jedem Makro zugreifen, die Ihre Fehlermeldungen machen und/oder Ausnahmen viel informativer der schlechten Leser versuchen, ein Problem zu debuggen.

Neben dem obigen Makro Wrapper Beispiel, ein anderes (komplizierter) Beispiel der gleichen Technik can be seen in the Plumatic Schema library, wo sie clojure.core/defn mit einer erweiterten Version wrap.


Sie können auch wünschen, diese Frage zur Klärung an sehen, wie Clojure verwendet den „anonymous“ var als Vermittler zwischen einem Symbol und einer Funktion: When to use a Var instead of a function?

+1

Nicht, dass es einen Unterschied macht, aber '(defn hallo [] (println "hi")) 'ist wirklich' (def hallo (fn hallo [] (println "hi")) ', da die Funktion rekursiv sein darf Der lokale Name scheint völlig vergessen zu sein. – Thumbnail

+0

@AlanThompson vielen dank! ich habe gerade gelernt, hier viel mehr als nur die punktuellen Frage, und diese Diskussion war wirklich ausgezeichnet – matanster

+0

@thumbnail und AlanThompson ich glaube nicht das ursprüngliche ersten paar Kommentare hinzufügen ausgetauscht jede info über die antwort wie gerade formuliert ... darf ich vorschlagen sie einfach jetzt (löschend) fallen zu lassen, da sie nach der aktualisierung der antwort etwas verwirrend sind, wenn ich sie nicht falsch interpretiert habe. – matanster

Verwandte Themen