2016-07-27 3 views
3

Hier Snippet:Warum funktioniert eine Funktion aus einem Makro, während eine andere nicht kompiliert werden kann?

(defmacro produce-constantly-fn [] 
    (constantly :value)) 

(defmacro produce-fn [] 
    (fn [& args] :value)) 

(defn test-fn [] 
    ((produce-fn))) 

;; during evaluation of form below it throws: 
;; java.lang.IllegalArgumentException 
;; No matching ctor found for class clojure.core$constantly$fn__4614 
(defn test-constantly-fn [] 
    ((produce-constantly-fn))) 

Warum letzte Funktion nicht kompiliert werden kann? Das Snippet kann als eine Art Makro-Missbrauch betrachtet werden, aber trotzdem ...

Antwort

5

Ich nehme an, Sie definierten Ihren Makro-Körper ohne zu zitieren und Sie sind neugierig, warum es in solch einer seltsamen Fehlermeldung resultiert. Wenn Sie wirklich ein Makro zum Aufruf (constantly :value) gemeint zu definieren, dann sollten Sie unter Angabe verwenden und es wird funktionieren:

(defmacro produce-constantly-fn [] 
    `(constantly :value)) 

(defn test-constantly-fn [] 
    ((produce-constantly-fn))) 

=> #'user/test-constantly-fn 

(test-constantly-fn) 
=> :value 

Nun zu Ihrem Fall gehen zurück, ohne zu zitieren. Es sieht wirklich interessant und geheimnisvoll aus, also habe ich etwas gegraben. Dies sind meine Ergebnisse:

Wenn Sie ein Makro definieren:

(defmacro produce-constantly-fn [] 
    (constantly :value)) 

es wird nur eine Funktion erstellen namens produce-constantly-fn und markieren Sie es als Makro (es ist immer noch eine Clojure-Funktion).

Wenn Sie die Umsetzung aussehen in der constantly werden Sie (docs und Meta weggelassen) finden:

(defn constantly [x] 
    (fn [& args] x)) 

Unter der Haube wird es zu einem Verschluss Objekt erstellen, die IFn implementieren und wird einen einzigen Konstruktor Parameter haben über x Parameter zu schließen. So etwas wie dies in Java-Code:

public class clojure.core$constantly$fn__4614 { 
    private final Object x; 
    public clojure.core$constantly$fn__4614(Object x) { 
     this.x = x; 
    } 

    public Object invoke(...) { 
     return x; 
    } 
    // other invoke arities 
} 

Nun, wenn Sie folgende sexp haben:

(defn test-constantly-fn [] 
    ((produce-constantly-fn))) 

Ich bemerkte, dass Clojure Leser evals (produce-constantly-fn), die nur ein Funktionsobjekt zurückgeben soll (erzeugt durch den Aufruf (constantly :value)), aber ich im Debugger gefunden, dass es clojure.core$constantly$fn__4614. Symbol stattdessen erzeugt (beachten Sie die . am Ende des Symbols - es ist ein Java-Interop-Formular zum Aufruf eines Konstruktors). Es sieht so aus, als ob ein Funktionsobjekt/Wert irgendwie in ein Symbol umgewandelt wird, das seinen Konstruktoraufruf darstellt. Ich könnte finden, dass der Funktionswert in Compiler$InvokeExpr Objekt umgewandelt wird, das Verweise auf den kompilierten Klassennamen enthält, der wahrscheinlich irgendwie in das Symbol umgewandelt wird.

Der Leser versucht clojure.core$constantly$fn__4614. weiter aufzulösen. Es wird vom Leser in einen Aufruf an clojure.core$constantly$fn__4614clojure.core$constantly$fn__4614 Klassenkonstruktor ohne Parameter umgewandelt.

Wie Sie oben den Konstruktor dieser Klasse gesehen haben muss genau ein Konstruktor damit die Kompilierung fehlschlägt (in clojure.lang.Compiler.NewExpr Konstruktorrumpf) mit:

java.lang.IllegalArgumentException: No matching ctor found for class clojure.core$constantly$fn__4614 

Ich bin nicht sicher, warum die Clojure Leser auf den Funktionswert transformiert ein Symbol mit Konstruktor Aufruf Interop Formular und verursacht solches Verhalten, so dass ich nur die direkte Ursache der Fehlermeldung und nicht die Ursache, warum Ihr Code nicht funktioniert. Ich denke, es könnte ein Fehler sein oder eine bewusste Designentscheidung. Von den Makroautoren wäre es besser, schnell zu versagen und zu erfahren, dass der Rückgabewert eines Makros keine gültigen Codedaten ist, aber andererseits kann es sehr schwierig oder unmöglich sein festzustellen, ob die zurückgegebenen Daten ein gültiger Code sind oder nicht . Es lohnt sich, auf der Clojure-Mailingliste nachzusehen.

+0

Ich schätze Ihre Untersuchung sehr. Vielen Dank! Auch bei Ihren Annahmen hatten Sie Recht: Ich verzichtete absichtlich auf Makrodefinition. – OlegTheCat

+0

Gern geschehen. Es sah so interessant aus, dass es sich lohnt zu überprüfen, was vor sich geht. –

+0

BTW, welche Art von Debugger haben Sie für die Untersuchung verwendet? – OlegTheCat

0

Ich denke, was los ist: Makros Auflösung vor Funktionen.

Also, wenn Sie macroexpand-1 auf die Funktion rufen Sie erhalten:

(def test-constantly-fn (clojure.core/fn ([] ((produce-constantly-fn))))) 

So ((produce-constantly-fn)) wird, bevor Funktion aufgerufen werden und zeigt Fehler aufgelistet.

Verwandte Themen