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投票
by

评论者:alexmiller

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

0投票
by

评论者:abram

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

0投票
by

评论者:alexmiller

以下是一些可以用来说明问题的Java代码(具有静态初始化器的类)和Clojure代码(用于扩展它的gen-class和proxy),这些都是测试描述中可用的代码。不应该有任何依赖于iOS或其他外部依赖项。

0投票
by

评论者:abram

已添加示例代码,请告诉我是否需要添加其他内容!

0投票
by

评论者:abram

出于好奇,这个修复有望被包含在1.8中吗?

0投票
by

评论者:alexmiller

未知。

0投票
by

评论者:notespin

我也受这个虫害影响。一个在某个命名空间中的函数调用了一个在原地初始化的静态Java变量。另一个通过genclass创建的命名空间调用该函数。现在在编译时,静态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

用户=> (def lcp (proxy (javafx.scene.control.ListCell) () )))

编译器异常: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

用户=> (def jfxpanel (javafx.embed.swing.JFXPanel.))

'user/jfxpanel

用户=> (def lcp (proxy (javafx.scene.control.ListCell) () )))

'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”。
  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(由abram报告)
...