Embedding custom objects in Clojure code

I want to embed a Java object (in this case, BufferedImage) in Clojure code, which could later be eval d.

Generating 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 a way to do something like this work?

EDIT:

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

I need to generate the quoted code because it is part of a much larger code generation library that will apply further transformations to the generated code, so I can’t just do something like:

 (defn f [image] (fn [] (.getRGB image 0 0))) 
+5
source share
3 answers

Not sure why you need this, but you can create code that parses 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 through the generated objects - you can create code that removes the link to eval, but then you can evaluate it only once.

Edit : A leak can be prevented by the following (evil!) Hack:

The above code bypasses the eval compiler by storing the reference to the object outside while creating the code. It can be put off. The object reference can be stored in the generated code when the compiler bypasses the macro. Storing the link in code means the garbage collector is working fine.

A key is a macro that wraps an object. It does what the original decision made (i.e., it stored the object from the outside to bypass the compiler), but immediately before compilation. The generated expression retrieves the external link and then removes it to prevent leaks.

Note: This is evil . Macro expansion time is the least desirable place for global side effects, and this is exactly what this decision does.

Now for the code:

 (def objs (atom {})) 

Here, where objects are temporarily stored, using 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 an evil macro that resides in the generated code, storing an object reference in x and a unique key in sym . Before compilation, it stores the object in an external dictionary under the sym key and generates code that extracts it, removes the external link, and returns the restored 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 unusual, just wrap the object in an evil macro along with a unique key.

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

+2
source

I think you will need to write a macro that transfers the object (or the way to create the desired object) at compile time, serializes this object in binary format (byte array), and the macro output should be a character that refers to the byte array and function, which can be used to obtain an object from serialized data by de-serializing it.

+3
source

why not: (defmacro m [img] `(.getRGB ~ img 0 0)) then u can write: (m some-buffered-image)

The difference is that f is a function, so its argument will be evaluated before its body is processed. Thus, the image object itself will be placed in the generated code. But for macros, their arguments will not be evaluated. Thus, only a buffered character will be placed inside the code. The generated code will be: (.getRGB some-buffered-image 0 0). Just as directly write the source code. I think you want to.

But why can't I put an object in the generated code? Answer: yes, and it can. What the exception message says is not true. u can embed some kinds of objects in the generated code, but not all their kinds. These include character, number, character, string, patten regex, keyword, boolean, list, map, set, etc. All of these objects will be understood by the Clojure compiler. They are similar to keywords, operators, and literals in other languages. u cannot require the Clojure compiler to know all kinds of objects, just as it cannot require the C or Java compiler to know all words not included in its syntax.

0
source

Source: https://habr.com/ru/post/916726/


All Articles