Embedding arbitrary objects in Clojure code

陌路散爱 提交于 2019-12-05 11:39:46

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.

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.

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 being evaluated. Thus the image object itself will be placed within the generated code. But for macros, their arguments will not be evaluated. So only the symbol some-buffered-image will be placed within the code. The generated code will be: (.getRGB some-buffered-image 0 0). Just like u write the source code directly. I think that's what u want.

But why can't I place an object in the generated code? The answer is: yes, u can. What the exception message says is not the truth. u can embed some kinds of objects in generated code, but not all kinds of them. They include symbol, number, character, string, regex patten, keyword, boolean, list, map, set etc. All these objects will be understood by the Clojure compiler. They are like key words, operators and literals in other languages. u can't require the Clojure compiler knows all kinds of objects, just like u can't require a C or Java compiler knows all words not included by its syntax.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!