请分享您的看法,参加2024 Clojure现状调查!

欢迎!有关此页面如何工作的更多信息,请参阅关于页面。

+1投票
编译器

我正在使用Clojure和RoboVM的项目中工作。我们使用AOT编译将Clojure编译成JVM类,然后使用RoboVM将JVM类编译成本地代码。在我们的Clojure代码中,我们调用RoboVM提供的Java API,这些API包装了本地的iOS API。

但我们发现了一个与继承和类级别静态初始化代码相关的错误。许多iOS API需要从基类继承,然后重写某些方法。目前,Clojure在编译时运行父类的静态初始化代码,无论使用":gen-class"还是"proxy"创建子类。然而,RoboVM的基础"ObjCObject"类(链接:1),大多数iOS特定类都从中继承,它需要iOS运行时初始化,并且在代码不运行在设备上时会抛出编译时错误。

CLJ-1315通过修改"import"来加载类而不运行静态初始化代码解决了类似的问题。我已编写了自己的补丁,该补丁将此行为扩展到":gen-class"和"proxy"。单元测试已经通过,并且我们在iOS应用程序中成功使用了此代码。

补丁: clj-1743-2.patch

以下是一些用于演示当前行为的示例代码(完整演示项目在(链接:https://github.com/figly/clojure-static-initialization))

`
包 clojure_static_initialization;

public class Demo {
static {

System.out.println("Running static initializers!");

}
public Demo () {
}
}
`

(ns clojure-static-initialization.gen-class-demo (:gen-class :extends clojure_static_initialization.Demo))

`
(ns clojure-static-initialization.proxy-demo)

(defn make-proxy []
(proxy [clojure_static_initialization.Demo] []))
`

链接:1 https://github.com/robovm/robovm/blob/master/objc/src/main/java/org/robovm/objc/ObjCObject.java

14 个回答

0 投票

评论者:alexmiller

与之前没有变化,只是更新到1.7.0-RC2的master分支。

0 投票

评论者:alexmiller

如果你有�试代理和gen-class的草图,那会很有帮助。

0 投票

评论者:abram

当然,你希望草图代码是什么形式?一个小的独立项目?单元测试?

0 投票

评论者:alexmiller

这里有一段Java代码(包含打印静态初始化器的类)和Clojure代码(用于扩展它的gen-class和proxy)封装在测试描述中,可以用来演示问题。不应该有任何iOS或其他外部依赖。

0 投票

评论者:abram

已添加示例代码,请告知我是否可以添加其他内容!

0 投票

评论者:abram

只是出于好奇,这有机会进入1.8吗?

0 投票

评论者:alexmiller

未知。

0 投票

评论者:notespin

我也受到了这个错误的困扰。一个命名空间中的函数调用了在原地初始化的静态Java变量。另一个被genclassed称呼的命名空间调用了该函数。现在在编译时,静态Java被初始化了,这导致了构建失败,因为该静态Java初始化需要的资源在构建机器上不存在。

0 投票

评论者:michaelblume

刷新补丁以便于应用到master分支,没有变化,保持归属。

0 投票

评论者:alexmiller

我对在RT.loadClassForName()中进行的改动以及Compiler中对RT.classForNameNonLoading()的调用感到困惑。这是补丁漂移吗?还是发生了什么?

0 投票

评论者:sonicsmooth

感谢你发布这个补丁。静态初始化器的问题使得在使用AOT和交互式开发进行JavaFX开发时感到困难。我今天克隆了Clojure 1.9.0-master源代码并应用了补丁,但是示例Clojure项目仍然显示“正在运行静态初始化!”我已经通过我的实际用法验证了这一点。如果首先启动JFXPanel,错误将会消失。截至2017年9月,有其他方法定义代理或延迟到运行时的方式来解决这个问题吗?谢谢你。

$ lein clean;lein repl
编译1个源文件到C:\dev\clojure\clojure-static-initialization\target\classes
编译clojure-static-initialization.gen-class-demo
编译clojure-static-initialization.proxy-demo
运行静态初始化器!
Clojure 1.9.0-master-SNAPSHOT

user=> (def lcp (proxy (link: javafx.scene.control.ListCell) (link: )))

编译器异常java.lang.ExceptionInInitializerError,编译位置:(C:\dev\clojure\clojure-static-initialization\target\f31ee90298a1be447b450330204c3c0806c08b96-init.clj:1:10)

$ lein clean;lein repl
编译1个源文件到C:\dev\clojure\clojure-static-initialization\target\classes
编译clojure-static-initialization.gen-class-demo
编译clojure-static-initialization.proxy-demo
运行静态初始化器!
Clojure 1.9.0-master-SNAPSHOT

user=> (def jfxpanel (javafx.embed.swing.JFXPanel.))

'user/jfxpanel

user=> (def lcp (proxy (link: javafx.scene.control.ListCell) (link: )))

'user/lcp

0 投票

评论者:[email protected]

我不太确定这个修正只是简单地交换调用{{classForNameNonLoading}}几个地方。{{proxy}}进行了一些相当复杂的内省和类分析,这很大程度地影响了静态代码。

另一种解决方案是有一个代理函数——一个与{{proxy}}做相同操作但在运行时执行的函数。
我目前有一个权宜之计可以解决JavaFX的ListCell问题...

0 投票

评论者:[email protected]

权宜之计:将宏评估延迟从编译时到运行时。
在这种情况下,我假设你已经在一个函数中封装了代理调用,并且在运行时这样做是安全的(因为你已经初始化了JavaFX或类似的东西)
1. 使用“反引号”来防止宏在编译时评估。
2. 将你的反引号代码包裹在eval中

(defn make-thing [] (eval `(proxy ...

  1. 局部绑定和函数参数需要“gensym-ed” 1. .
  2. 隐式的{{this}}需要作为~'this来访问

`
(defn make-thing []
(eval
`(proxy [ListCell][]

 (updateItem [item# empty?#]
   (proxy-super updateItem item# empty?#)            
     (.setText ~'this nil)
     ...

`

  1. 传递给函数的参数需要在外部动态绑定,并且可能在反引号代码中的let绑定时重新绑定,以便在不同的线程中访问

`
(def ^:dynamic an-arg nil)

(defn make-thing [an-arg]
(binding [*an-arg* an-arg]

(eval
`(let [an-arg# *an-arg*]
  (proxy [ListCell][]
    (updateItem [item# empty?#]
      (proxy-super updateItem item# empty?#)            
       (.setText ~'this nil)
       (println "an-arg:" an-arg#)
       ...

`

这可以用宏来实现吗?例如 proxyfn

0 投票
参考: https://clojure.atlassian.net/browse/CLJ-1743(由 Abram 报告)
...