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 {
静态 {

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 测试此功能的 sketch,将会很有帮助。

0

评论人:abram

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

0

评论者:alexmiller

在这里的测试说明中,有一些 Java 代码(具有静态初始化器并打印输出的类)和 Clojure 代码(用于扩展它的 gen-class 和 proxy),可以用来展示该问题。不应有任何依赖 iOS 或其他外部依赖。

0

评论人:abram

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

0

评论人:abram

出于好奇,这个功能有进入 1.8 的可能性有多大?

0

评论者:alexmiller

未知。

0

评论人: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

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. 传递给函数的参数需要在 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 提出)
...