2024 Clojure 状态调查! 中分享您的观点。

欢迎!有关 Clojure 的工作原理,请参阅 关于 页面以获取更多信息。

+1 投票
编译器

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

但我们发现了一个与继承和类级别静态初始化代码相关的问题。许多 iOS API 需要继承自基对象并重写某些方法。目前,Clojure 使用 ":gen-class" 或 "proxy" 创建子类时,无论是否使用 ":gen-class" 或 "proxy",都会在编译时运行超类的静态初始化代码。然而,大多数 iOS 特定类继承的 RoboVM 的基 "ObjCObject" 类(链接:1)需要 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版本的主分支。

0 投票

评论由:alexmiller 发表

如果你有一个使用代理和gen-class进行测试的草图,那将很有帮助。

0 投票

评论者:abram

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

0 投票

评论由:alexmiller 发表

在这里的测试描述中包含了几个Java行(一个有静态初始化器的类和输出)以及Clojure代码(用于扩展它的gen-class和proxy)来展示问题。不应有任何依赖iOS或其他外部依赖。

0 投票

评论者:abram

已添加示例代码,如果我可以提供其他帮助请告诉我!

0 投票

评论者:abram

出于好奇,这个功能有希望被集成到1.8版本的概率有多大?

0 投票

评论由:alexmiller 发表

未知。

0 投票

评论者:notespin

我也受到了这个bug的影响。命名空间中的一个函数调用了在位置初始化的静态Java变量。另一个使用genclass创建的命名空间调用了该函数。现在在编译时,静态Java被初始化,导致构建失败,因为那个静态Java初始化需要构建机器上不存在的资源。

0 投票
by

评论者:michaelblume

刷新补丁以将其应用到master分支,无改动,保持归属。

0 投票
by

评论由:alexmiller 发表

我对补丁在RT.loadClassForName()中进行的更改感到困惑,但Compiler中的更改是调用RT.classForNameNonLoading()吗?这是补丁偏移还是怎么回事?

0 投票
by

评论者: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 投票
by

评论者:[email protected]

我不太确定修复的问题仅仅是交换几个地方的classForNameNonLoading调用。

另一种解决方案是有一个代理函数——一个在运行时与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. 传递给函数的参数需要在eval外部动态绑定,可能在反引号代码中的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](https://clojure.atlassian.net/browse/CLJ-1743)(由 Abram 报告)
...