6

我想在 Clojure 代码中嵌入一个 Java 对象(在本例中为 BufferedImage),eval以后可以使用它。

创建代码工作正常:

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

但是,尝试eval它时会出现异常:

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

有没有办法让这样的工作?

编辑:

我尝试这样做的原因是我希望能够生成从图像中获取样本的代码。图像被传递给执行代码生成的函数(相当于f上面的),但是(由于各种原因)以后不能作为参数传递给编译的代码。

我需要生成带引号的代码,因为这是一个更大的代码生成库的一部分,它将对生成的代码应用进一步的转换,因此我不能只做类似的事情:

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

3 回答 3

3

我猜你需要编写一个宏,在编译时获取对象(或创建所需对象的方法),以二进制格式(字节数组)序列化该对象,宏的输出应该是 - 一个引用的符号到字节数组和一个函数,该函数可用于通过反序列化从序列化数据中获取对象。

于 2012-05-24T11:45:29.193 回答
3

不确定你需要它做什么,但你可以使用以下作弊方法创建对任意对象进行 eval 的代码:

(def objs (atom []))


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

那么你就可以:

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

这只是一个概念证明,它会泄漏正在生成的对象 - 您可以生成删除 eval 引用的代码,但您只能对其进行一次 eval。

编辑:可以通过以下(邪恶的!)黑客来防止泄漏:

上面的代码通过在生成代码时将对象引用存储在外部来绕过 eval 的编译器。这可以推迟。对象引用可以存储在生成的代码中,而编译器被宏绕过。在代码中存储引用意味着垃圾收集器正常工作。

关键是包装对象的宏。它做了原始解决方案所做的事情(即在外部存储对象以绕过编译器),但就在编译之前。生成的表达式检索外部引用,然后删除以防止泄漏。

注意:这是邪恶的。宏的扩展时间是最不希望出现全局副作用的地方,而这正是该解决方案所做的。

现在看代码:

(def objs (atom {}))

这里是临时存储对象的地方,由唯一键(字典)键控。

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

这是位于生成代码中的邪恶宏,将对象引用x和唯一键保存在sym. 在编译之前,它将对象存储在键下的外部字典中,sym并生成检索它的代码,删除外部引用并返回检索到的对象。

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

没什么花哨的,只是将对象包装在 evil 宏中,并加上一个唯一的键。

当然,如果您只扩展宏而不评估其结果,您仍然会得到泄漏。

于 2012-05-24T18:50:12.537 回答
0

为什么不: (defmacro m [img] `(.getRGB ~img 0 0)) 然后你可以写: (m some-buffered-image)

不同之处在于 f 是一个函数,因此它的参数将在其主体被评估之前被评估。因此,图像对象本身将被放置在生成的代码中。但是对于宏,它们的参数不会被评估。所以只有符号 some-buffered-image 将被放置在代码中。生成的代码将是:(.getRGB some-buffered-image 0 0)。就像你直接写源代码一样。我想这就是你想要的。

但是为什么我不能在生成的代码中放置一个对象呢?答案是:是的,你可以。异常消息所说的不是事实。您可以在生成的代码中嵌入某些类型的对象,但不是所有类型的对象。它们包括符号、数字、字符、字符串、正则表达式模式、关键字、布尔值、列表、映射、集合等。所有这些对象都将被 Clojure 编译器理解。它们就像其他语言中的关键字、运算符和文字。你不能要求 Clojure 编译器知道所有类型的对象,就像你不能要求 C 或 Java 编译器知道所有不包含在其语法中的单词一样。

于 2013-10-09T14:34:29.513 回答