这是我在邮件列表https://groups.google.com/forum/#!topic/clojure/Ozt5HQyM36I上的帖子的一个副本。基于那里的回复,我不确定这是应该记录为增强功能还是缺陷。请将指定转换为适当的类别。
我发现当使用ns-unmap时,在AOT编译的Clojure中似乎有一个错误;这个根本原因可能影响重新定义Vars的其他代码。我有一个以下简化案例
(ns unmap-test.core)
(def a :test-1)
(ns-unmap 'unmap-test.core 'a)
(def a :test-2)
结果是,在这个命名空间加载时a无法解析。当查看编译后的字节码时
似乎发生以下操作
- A调用RT.var与'unmap-test.core和'a返回一个Var,该Var绑定到一个常量
在此调用期间,将这个var添加到命名空间的映射中
- 与1相同。
- Var 1绑定到: test-1
- 调用ns-unmap。
- Var 2绑定到: test-2。
免责声明:这是我第一次有机会直接查看字节码,我可能遗漏了某些东西。
这里的基本问题是var可以从前面的加载方法中访问,但在步骤5执行时,var不能从命名空间映射中访问。因此,Var的根设置为: test-2,但这个Var并没有从命名空间映射过来。
当没有AOT编译时,这也可以正常运行,以及当使用
(intern 'unmap-test.core 'a :test-2)
(ns unmap-test.core)
(def a :test-1)
(ns-unmap 'unmap-test.core 'a)
时。
我明白在Clojure中创建defs、取消映射它们然后重新创建它们通常是不好的做法。
在我们这里有一个特殊情况,我们需要在同一命名空间中有一个接口和一个同名的Var。仅仅
使用definterface然后def会导致编译错误
user=> (definterface abc)
user.abc
user=> (def abc 1)
CompilerException java.lang.RuntimeException: 期望var,但abc已映射到接口 user.abc,编译:( /private/var/folders/3m/tvc28b5d7p50v5_8q5ntj0pmflbdh9/T/form-init4734176956271823921.clj:1:1 )
不涉及太多细节,这基本上是一个黑客手段,允许我们在不立即更改大量下游消费代码的情况下重构我们的内部框架代码
。我们在宏展开期间删除了对接口的使用
但它仍然需要在类路径上存在,以便它可以被下游命名空间导入。
有其他多种方法可以实现这一点,所以这对我们来说不是一个特别大问题,但我认为提出这个问题是值得的。
这是我尝试的第一件事,当我发现它没有工作时很惊讶。
请注意,我在我的示例项目中使用了Clojure 1.8.0 RC4版本,但在1.7.0中也出现了相同的行为。
相关链接
- 初始化类load方法的字节码:[链接](https://gist.github.com/WilliamParker/d8ef4c0555a30135f35a)
- init0方法的字节码:[链接](https://gist.github.com/WilliamParker/dc606ad086670915efd9)
- 初始化类的反编译Java代码。请注意,根据我的了解,这并不完全与字节码同步,
但相对于字节码,这是一种更快的方式,可以了解所发生的事情。
[链接](https://gist.github.com/WilliamParker/4cc47939f613d4595d94)
- 包含上述代码的简单项目:[链接](https://github.com/WilliamParker/unmap-test)
请注意,如果在不进行AOT编译的情况下尝试,应删除任何先前编译的任何类目标文件夹。