Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Embedding arbitrary objects in Clojure code

I want to embed a Java object (in this case a BufferedImage) in Clojure code that can be evald later.

Creating the code works fine:

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

(f some-buffered-image)
=> (.getRGB #<BufferedImage BufferedImage@5527f4f9: 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)

However you get an exception when trying to eval it:

(eval (f some-buffered-image))
=> CompilerException java.lang.RuntimeException: Can't embed object in code, maybe print-dup not defined: BufferedImage@612dcb8c: 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) 

Is there any way to make something like this work?

EDIT:

The reason I am trying to do this is that I want to be able to generate code that takes samples from an image. The image is passed to the function that does the code generation (equivalent to f above), but (for various reasons) can't be passed as a parameter to the compiled code later.

I need to generate quoted code because this is part of a much larger code generation library that will apply further transforms to the generated code, hence I can't just do something like:

(defn f [image] 
  (fn [] (.getRGB image 0 0)))
like image 384
mikera Avatar asked May 24 '12 10:05

mikera


2 Answers

Not sure what you need it for, but you can create code that evals to an arbitrary object using the following cheat:

(def objs (atom []))


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

Then you can:

> (eval (make-code-that-evals-to *out*))
#<PrintWriter java.io.PrintWriter@14aed2c>

This is just a proof of concept and it leaks the objects being produced - you could produce code that removes the reference on eval but then you could eval it only once.

Edit: The leaks can be prevented by the following (evil!) hack:

The above code bypasses eval's compiler by storing the object reference externally at the time the code is generated. This can be deferred. The object reference can be stored in the generated code with the compiler being bypassed by a macro. Storing the reference in the code means the garbage collector works normally.

The key is the macro that wraps the object. It does what the original solution did (i.e. store the object externally to bypass the compiler), but just before compilation. The generated expression retrieves the external reference, then deletes to prevent leaks.

Note: This is evil. Expansion-time of macros is the least desirable place for global side-effects to occur and this is exactly what this solution does.

Now for the code:

(def objs (atom {}))

Here's where will temporarily store the objects, keyed by unique keys (dictionary).

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

This is the evil macro that sits in the generated code, keeping the object reference in x and a unique key in sym. Before compilation it stores the object in the external dictionary under the key sym and generates code that retrieves it, deletes the external reference and returns the retrieved object.

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

Nothing fancy, just wrap the object in the evil macro, together with a unique key.

Of course if you only expand the macro without evaluating its result, you'll still get a leak.

like image 169
Rafał Dowgird Avatar answered Oct 21 '22 02:10

Rafał Dowgird


I guess you would need to write a macro that takes the object (or a way to create the required object) at compile time, serialize that object in binary format (byte array) and the output of the macro should be - a symbol that refer to the byte array and a function that can be used to get the object from the serialized data by de-serializing it.

like image 32
Ankur Avatar answered Oct 21 '22 01:10

Ankur