2012-05-24 8 views
5

Ich möchte ein Java-Objekt (in diesem Fall ein BufferedImage) in Clojure-Code einbetten, die später eval d sein kann.Einbetten beliebiger Objekte in Clojure-Code

den Code erstellen funktioniert:

(defn f [image] 
    `(.getRGB ~image 0 0)) 
=> #'user/f 

(f some-buffered-image) 
=> (.getRGB #<BufferedImage [email protected]: type = 2 DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff amask=ff000000 IntegerInterleavedRaster: width = 256 height = 256 #Bands = 4 xOff = 0 yOff = 0 dataOffset[0] 0> 0 0) 

jedoch eine Ausnahme erhalten, wenn sie versuchen, es eval:

(eval (f some-buffered-image)) 
=> CompilerException java.lang.RuntimeException: Can't embed object in code, maybe print-dup not defined: [email protected]: type = 2 DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff amask=ff000000 IntegerInterleavedRaster: width = 256 height = 256 #Bands = 4 xOff = 0 yOff = 0 dataOffset[0] 0, compiling:(NO_SOURCE_PATH:1) 

Gibt es eine Möglichkeit etwas wie diese Arbeit zu machen?

EDIT:

Der Grund, warum ich dies zu tun versuche, ist, dass ich Code zu erzeugen in der Lage sein wollen, die Proben von einem Bild nimmt. Das Bild wird an die Funktion übergeben, die die Codegenerierung durchführt (entspricht f oben), kann aber (aus verschiedenen Gründen) später nicht als Parameter an den kompilierten Code übergeben werden.

muß ich zitierte Code generieren, weil dieser Teil einer viel größeren Codegenerierung Bibliothek ist, die weiteren Transformationen auf den generierten Code gelten, daher kann ich nicht einfach so etwas tun:

(defn f [image] 
    (fn [] (.getRGB image 0 0))) 
+0

ist es nur eval, das diesen Fehler gibt? Makros (zB 'if') können es gut gebrauchen, nehme ich an? Wenn ja, dann ist es wahrscheinlich, weil eval die Dinge zwingt, durch Text zu gehen (was für eval sinnvoll ist). Wenn dies ein Problem für Sie ist, verwenden Sie möglicherweise eval, wenn es nicht notwendig ist - wenn ein Makro möglicherweise das ist, was Sie brauchen. –

+0

@andrewcooke Wie ich es verstehe, zwingt das 'eval' die Dinge nicht wirklich durch * text *. Eval (via Compilieren) generiert JVM-Code, der die Objekte erstellt. Das Durchlaufen von Text (via Serialisierung) ist das letzte Mittel, das für unbekannte Objekte verwendet wird. –

+2

@mikera Zurücktreten, können Sie kommentieren, warum Sie quoting und eval vs. etwas tun wie '(defn f [a] (fn [] (.getRGB a 0 0)))? – user100464

Antwort

2

nicht sicher, was Sie es brauchen für, aber Sie können Code erstellen, die auf einem beliebigen Objekt evals mit dem folgenden Cheat:

(def objs (atom [])) 


(defn make-code-that-evals-to [x] 
    (let [ 
     nobjs (swap! objs #(conj % x)) 
     i (dec (count nobjs))] 
    `(nth ~i @objs))) 

Dann können Sie:

> (eval (make-code-that-evals-to *out*)) 
#<PrintWriter [email protected]> 

Dies ist nur ein Proof of Concept und es leckt die produzierten Objekte - Sie könnten Code erzeugen, der die Referenz auf eval entfernt, aber dann könnten Sie es nur einmal auswerten.

bearbeiten: (! Böse) Die Lecks, durch die folgende verhindert werden Hack:

Der obige Code umgeht Compilers eval durch den Objektverweis extern zu dem Zeitpunkt speichert der Code generiert wird. Dies kann aufgeschoben werden. Die Objektreferenz kann im generierten Code gespeichert werden, wobei der Compiler von einem Makro umgangen wird. Das Speichern des Verweises im Code bedeutet, dass der Garbage Collector normal funktioniert.

Der Schlüssel ist das Makro, das das Objekt umschließt. Es macht, was die ursprüngliche Lösung tat (d. H. Das Objekt extern speichern, um den Compiler zu umgehen), aber kurz vor dem Kompilieren. Der generierte Ausdruck ruft die externe Referenz ab und löscht sie, um Lecks zu vermeiden.

Hinweis: Dies ist böse. Die Expansionszeit von Makros ist der am wenigsten wünschenswerte Ort, an dem globale Nebenwirkungen auftreten können, und dies ist genau das, was diese Lösung tut.

Jetzt für den Code:

(def objs (atom {})) 

Hier ist, wo vorübergehend die Objekte speichern, verkeilt durch eindeutige Schlüssel (Wörterbuch).

(defmacro objwrap [x sym] 
    (do 
    (swap! objs #(assoc % sym x)) ; Global side-effect in macro expansion 
    `(let [o# (@objs ~sym)] 
     (do 
     (swap! objs #(dissoc % ~sym)) 
     o#)))) 

Das ist das Übel Makro, das in dem generierten Code sitzt, in x den Objektverweis zu halten und einen eindeutigen Schlüssel in sym. Vor der Übersetzung speichert es das Objekt im externen Wörterbuch unter dem Schlüssel sym und generiert Code, der es abruft, löscht die externe Referenz und gibt das abgerufene Objekt zurück.

(defn make-code-that-evals-to [x] 
    (let [s 17] ; please replace 17 with a generated unique key. I was lazy here. 
    `(objwrap ~x ~s))) 

Nichts Besonderes, wickelt nur das Objekt in dem bösen Makro, zusammen mit einem eindeutigen Schlüssel.

Natürlich, wenn Sie nur das Makro erweitern, ohne sein Ergebnis zu bewerten, erhalten Sie immer noch ein Leck.

+0

nette Idee!aber ich stimme dem Speicherleck macht dieses ein bisschen eklig – mikera

+0

@mikera Ich habe eine Idee, wie es zu beheben, wird bearbeiten, wenn die Zeit es erlaubt :) –

+0

@mikera Lösung hinzugefügt. Würde immer noch nach einem Weg um diese suchen, da dies scheint gegen den Strich gehen. –

3

Ich denke, Sie müssten einen Makro schreiben, der das Objekt (oder eine Möglichkeit, das erforderliche Objekt zu erstellen) zur Kompilierzeit übernimmt, dieses Objekt im Binärformat (Byte-Array) serialisiert und die Ausgabe des Makros sollte - ein Symbol sein, das sich auf das bezieht Byte-Array und eine Funktion, mit der das Objekt aus den serialisierten Daten durch De-Serialisierung abgerufen werden kann.

+0

Warum ist die Serialisierung notwendig? Der kompilierte Code hängt sowieso vom externen Byte-Array ab. Warum also nicht ein Objekt-Array erstellen und den Verweis auf das ursprüngliche Objekt dort behalten? –

+0

Grundsätzlich, wenn das Makro ausgewertet wird, wird die Kompilierzeit sein, für zB: '(embed-resource" meine-Datei.jpeg ")', liest die Makro Embed-Ressource die Datei und speicherte diese als Java-Array im Code Damit können Sie das Bild in den Code selbst einbetten – Ankur

+0

Danke, ich denke, ich bekomme es jetzt. Wenn ich das richtig verstehe, kann man das Objekt auch sofort beim Generieren des Codes serialisieren (in der Funktion 'f' im Beispiel von OP), oder? –

0

warum nicht: (defmacro m [img] `(.getRGB ~ img 0 0)) dann kann u schreiben: (m some-gepufferte-Bild)

Der Unterschied besteht darin, dass f eine Funktion ist , so wird sein Argument vor der Bewertung seines Körpers bewertet. Somit wird das Bildobjekt selbst innerhalb des generierten Codes platziert. Aber für Makros werden ihre Argumente nicht ausgewertet. Es wird also nur das Symbol some-buffered-image im Code platziert. Der generierte Code wird sein: (.getRGB some-buffered-image 0 0). So wie du den Quellcode direkt schreibst. Ich denke, das ist was du willst.

Aber warum kann ich ein Objekt nicht in den generierten Code einfügen? Die Antwort ist: Ja, du kannst. Was die Ausnahmebotschaft sagt, ist nicht die Wahrheit. Sie können einige Arten von Objekten in generierten Code einbetten, aber nicht alle Arten von Objekten. Sie enthalten Symbol, Zahl, Zeichen, String, Regex-Patten, Schlüsselwort, Boolean, Liste, Map, Set usw. Alle diese Objekte werden vom Clojure-Compiler verstanden. Sie sind wie Schlüsselwörter, Operatoren und Literale in anderen Sprachen. Sie können nicht verlangen, dass der Clojure-Compiler alle Arten von Objekten kennt, genauso wie Sie keinen C- oder Java-Compiler benötigen, der alle Wörter kennt, die nicht in seiner Syntax enthalten sind.