2012-05-24 9 views
5

Desidero incorporare un oggetto Java (in questo caso una BufferedImage) nel codice Clojure che può essere eval d successivo.Incorporamento di oggetti arbitrari nel codice Clojure

Creazione del codice funziona bene:

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

Tuttavia si ottiene un'eccezione quando si cerca di eval esso:

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

Esiste un modo per fare qualcosa di simile a questo lavoro?

EDIT:

Il motivo per cui sto cercando di fare è che voglio essere in grado di generare il codice che prende campioni da un'immagine. L'immagine viene passata alla funzione che genera il codice (equivalente a f sopra), ma (per vari motivi) non può essere passato come parametro al codice compilato in seguito.

ho bisogno per generare il codice citato perché questo fa parte di una libreria di generazione di codice molto più grande che si applica ulteriori trasformazioni al codice generato, quindi non posso fare qualcosa di simile:

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

è solo eval che dà questo errore? i macro (ad esempio 'if') possono usarlo bene, presumo? se è così, allora è probabilmente perché eval forza le cose a passare attraverso il testo (che ha senso per la valutazione). se questo è un problema per te, potresti usare eval quando non è necessario - quando una macro può essere ciò di cui hai bisogno. –

+0

@andrewcooke A quanto ho capito, 'eval' non impone realmente le cose per passare attraverso * text *. Eval (tramite compilazione) genera codice JVM che crea gli oggetti. Passare attraverso il testo (tramite serializzazione) è l'ultima risorsa utilizzata su oggetti sconosciuti. –

+2

@mikera Tornando indietro, puoi commentare perché hai scelto di usare quoting ed eval rispetto a qualcosa di simile a '(defn f [a] (fn [] (.getRGB a 0 0)))'? – user100464

risposta

2

Non sei sicuro di quello che ti serve per, ma è possibile creare codice che evals a un oggetto arbitrario usando la seguente trucco:

(def objs (atom [])) 


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

Quindi è possibile:

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

questo è solo un prova del concetto e perde gli oggetti che si stanno producendo - potresti produrre un codice che rimuova il riferimento su eval ma poi potresti evitarlo solo una volta.

Edit: (! Male) Le perdite possono essere evitati dal seguente incidere:

Il codice sopra bypassa compilatore eval memorizzando il riferimento all'oggetto esternamente al momento della generazione del codice. Questo può essere rinviato. Il riferimento all'oggetto può essere memorizzato nel codice generato con il compilatore escluso da una macro. Memorizzando il riferimento nel codice significa che il garbage collector funziona normalmente.

La chiave è la macro che avvolge l'oggetto. Fa ciò che ha fatto la soluzione originale (ad esempio, memorizza l'oggetto esternamente per aggirare il compilatore), ma appena prima della compilazione. L'espressione generata recupera il riferimento esterno, quindi elimina per evitare perdite.

Nota: Questo è il male. Il tempo di espansione delle macro è il posto meno auspicabile per gli effetti collaterali globali e questo è esattamente ciò che fa questa soluzione.

Ora, per il codice:

(def objs (atom {})) 

Ecco dove saranno memorizzare temporaneamente gli oggetti, calettati con chiavi univoche (dizionario).

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

Questa è la macro male che siede nel codice generato, mantenendo il riferimento all'oggetto in x e una chiave unica in sym. Prima della compilazione memorizza l'oggetto nel dizionario esterno sotto la chiave sym e genera il codice che lo recupera, elimina il riferimento esterno e restituisce l'oggetto recuperato.

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

Niente di particolare, basta avvolgere l'oggetto nella macro malvagia, insieme a una chiave univoca.

Ovviamente se si espande la macro senza valutare il risultato, si otterrà comunque una perdita.

+0

bella idea!ma sono d'accordo che la perdita di memoria rende questo un po 'brutto – mikera

+0

@mikera Ho idea di come aggiustarlo, cambierò quando il tempo lo consente :) –

+0

@mikera Soluzione aggiunta. Cercherei ancora un modo per aggirare questo, in quanto questo sembra andare contro il grano. –

3

immagino avresti bisogno di scrivere una macro che prende l'oggetto (o un modo per creare l'oggetto richiesto) in fase di compilazione, serializza quell'oggetto in formato binario (array di byte) e l'output della macro dovrebbe essere - un simbolo che si riferisce al array di byte e una funzione che può essere utilizzata per ottenere l'oggetto dai dati serializzati deserializzandolo.

+0

Perché la serializzazione è necessaria? Il codice compilato dipende comunque dall'array di byte esterno, quindi perché non renderlo un array di oggetti e mantenere semplicemente il riferimento all'oggetto originale lì? –

+0

Fondamentalmente quando verrà valutata la macro che sarà compilata, Per ex: '(embed-resource" my-file.jpeg ")', la macro embed-resource leggerà il file e lo memorizzerà come un array java nel codice , questo ti permetterà di incorporare l'immagine nel codice stesso – Ankur

+0

Grazie, penso di averlo capito ora. Se ho capito bene, puoi anche serializzare l'oggetto immediatamente quando generi il codice (nella funzione 'f' nell'esempio di OP), giusto? –

0

perché no: (defmacro m [img] `(~ .getRGB img 0 0)) allora u può scrivere: (m alcuni-buffered-immagine)

La differenza è che f è una funzione , quindi il suo argomento sarà valutato prima che il suo corpo venga valutato. Pertanto, l'oggetto immagine stesso verrà inserito all'interno del codice generato. Ma per le macro, i loro argomenti non saranno valutati. Quindi solo il simbolo un'immagine con buffering verrà inserito nel codice. Il codice generato sarà: (.getRGB some-buffered-image 0 0). Proprio come si scrive direttamente il codice sorgente. Penso che sia quello che vuoi.

Ma perché non posso posizionare un oggetto nel codice generato? La risposta è: sì, puoi. Quello che dice il messaggio di eccezione non è la verità. puoi incorporare alcuni tipi di oggetti nel codice generato, ma non tutti i tipi di essi. Includono simbolo, numero, carattere, stringa, regex patten, parola chiave, booleano, lista, mappa, insieme ecc. Tutti questi oggetti saranno compresi dal compilatore Clojure. Sono come parole chiave, operatori e letterali in altre lingue. Non puoi richiedere che il compilatore Clojure conosca tutti i tipi di oggetti, proprio come non puoi richiedere che un compilatore C o Java sappia tutte le parole non incluse nella sua sintassi.