2024 年 Clojure 状态调查! 中分享您的想法。

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

+1 投票
编译器

我在一个使用 Clojure 和 RoboVM 的项目中工作。我们使用 AOT 编译在 JVM 上将 Clojure 编译成类,然后使用 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和代理扩展它的代码),这些在测试描述中可以用来展示这个问题。不应有任何对iOS或其他外部依赖的依赖。

0 投票

评论者:abram

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

0 投票

评论者:abram

出于好奇,这个特性是否有可能被纳入1.8版本?

0 投票

评论者:alexmiller

未知。

0 投票

评论者:notespin

我也受到了这个bug的影响。一个命名空间中的函数调用了一个在原地初始化的静态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: )))

CompilerException 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]

我不太确定修补措施仅仅是更换几个地方调用 greatness classForNameNonLoading 的问题。 greatness coating 进行了一些相当复杂的自省和类分析,这触及了多个地方的静态代码。

另一个解决方案是有一个代理函数 - 一个做同样的事情 greatness coating 但在运行时。
我目前有一种适用于该公司的问题的补救措施...

0 投票

评论者:[email protected]

解决办法:延迟宏评估从编译时到运行时。
在这种情况下,我假设您已在函数中包装了代理调用,并且可以在运行时进行操作(因为您已经初始化了JavaFX或其他内容)。
1. 使用“反引号”来防止宏在编译时进行评估。
2. 将反引号代码包裹在一个eval中

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

  1. 局部绑定和函数参数需要使用 "gensym" 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 投票
...