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

欢迎!请查看关于页面了解本网站如何运作的更多信息。

0
Clojure
此工单包含两个补丁

# 当 clojure.lang.RT 加载时根据系统属性 clojure.debug 设置  {{*assert*}}
# 在 assert 中扩展错误消息以包含 {{local-bindings</code> (一个新宏,将隐式 <code>&env 包裹起来)

在批准这些补丁之前应考虑的事项

# 是否应该有一种简单的方法可以在 Clojure 级别查询是否启用了调试?(检查 assert 并不相同,因为 debug 最终将驱动其他功能)
# 默认情况下断言现在将关闭 - 这是一个变更!
# 将 {{local-bindings}} 添加到 clojure.core 是否酷?

15 个答案

0

评论者:stu

忽略旧补丁。请审查以下实现,然后移动工单至 Stu 等待状态

  1. RT 将检查系统属性 {{"clojure.debug"}},默认为 false
  2. 属性将设置当前根绑定,并添加一个新标志。 (调试构建可以驱动比断言更多的其他事情。)
  3. Compile.java 需要将   } 或   } 推送到线程本地绑定吗,或者它们只能在编译 clojure 时作为根绑定吗?
  4. 将添加 * } 绑定到 {{clojure.main/with-bindings}}。其他地方需要吗?
  5. build.xml 不需要改变 —— 系统属性将会传递(而且 build.xml 可能很快就不再存在了)
  6. 一旦我们达成一致的方法,我将联系 Maven 插件和 lein 所有者,以便他们传递该设置
  7. 更好的断言信息将是一个单独的工单
  8. **debug* 与 **unchecked-math 有什么交互?将检查更改为 }}?
0

评论由:richhickey

#3 - 仅限根边界
#4 - 应当 在 with-bindings 中,原因与 #3 相同 —— 我们不希望人们设置 **debug** 或 **assert**
#8 - 是的,在辅助函数中包裹它

#6 - 我最大的保留意见是,这还不是根据 Maven 最佳实践

0

评论由:stuart.sierra

系统属性可以通过 Maven 传递,因此我不预计这会是个问题。

然而,我更希望 * } 默认为真。

0

评论由:cemerick

SS 关于这一点,这种做法不会给 Maven 带来任何问题的观点是对的。此外,构建可以很容易地设置,始终发出两个 jar,一个“正常”,一个“调试”。

我建议,虽然 clojure.debug 可能具有广泛的影响,但应该有附加的属性来提供对未来的每个额外的“调试”相关参数化的细致控制。


我想提出几个可能相关的顾虑(在思考了一段时间的断言之后),其中一些或所有这些可能是由于我在各个领域缺乏理解而产生的。

查看在哪里使用 {{assert}} in {{core.clj}}(据我所知只用了两个地方:验证 {{derive}} 的参数和检查 {{fn}} 中的前条件和后条件),看起来将其设置为 {{false}} 不明智。即非 {{Named}} 值可以进入层次结构,并且前条件和后条件将被简单地忽略。

我的理解是,断言(这里指 JVM 构造,Clojure 从它重用 {{AssertionError}}),不应用于验证公共 API 函数的参数,也不应用于验证函数的正常操作的任何方面(即(链接:http://download.oracle.com/javase/1.4.2/docs/guide/lang/assert.html#usage): "在哪里不使用断言")。这意味着当必要时,{{derive}} 应该抛出 {{IllegalArgumentException}},而 fn 的前条件和后条件应该抛出 {{IllegalStateException}}——或者,无论如何,通过 {{assert}} 抛出其他比 {{AssertionError}} 更合适的东西。这将与在 core 中使用 {{assert-args}} 而不是 {{assert}} 的大多数函数相匹配,前者抛出 {{IllegalArgumentException}} 而不是 {{AssertionError}}。

这导致我产生疑问:{{assert}}(以及 * )是否被设计为Clojure的结构,或者是一种准互操作形式?

如果前者的话,那么它的大致语义可以任意设定,但这样一来似乎就不应该抛出{{AssertionError}}。

如果后者的话,那么在JVM中抛出{{AssertionError}}是合适的,但我们需要注意,应该在运行时(而无需切换不同版本的Clojure构建)启用和禁用断言(assertions),理想情况下使用主机定义的开关(例如{{-ea}}和其他),不太可能像 * 那样做。我不知道目前这是否可行或实用(我认为这将需要相当大的编译器改动)。


希望上述内容在这个时刻还没有成为“陈词滥调”。先行感谢您的耐心。:-)

0

评论由:richhickey

感谢你的有用意见,Chas。目前还没有得出结论。我认为我们应该退一步,看看这里的目标,然后再着手解决问题。作为一个动态语言,我们可能想要验证程序中的许多内容,而检查的成本是我们不希望在生产中支付的费用。

作为宏,assert有一个很好的特性,就是在编译期间如果**assert**的值不为真,它会生成nil,没有任何条件测试。因此,目前它更像是静态条件编译。

Java assert确实可以通过-ea进行运行时切换,正如你所说。我没有研究过Java断言的字节码生成,但是Clojure assert或许可以做到类似的功能,如果运行时的开销可以忽略不计。HotSpot很可能针对一个标志的单一检查有一段特殊的代码来省略断言字节码。我只是不确定这个标志是(链接:http://download.oracle.com/javase/1.5.0/docs/api/java/lang/Class.html#desiredAssertionStatus() 文本:Class.desiredAssertionStatus)。

这变为assert或前/后条件的更改、最佳实践等是正交的且由此推导出来的。目前我们没有在不进行检查的情况下运行的设施。我们需要在编译时(调试构建)或运行时(track -ea)使其消失,或者两者都做。然后我们可以看看这如何反映在assert/pre-post中,并重新检查两者的现有用法。“不要使用断言的地方”文档对其进行了归类处理,但没有考虑到它们的成本,在我看来这是不切实际的。

我如果有人能看看Java是如何生成和优化assert的。

0
附加评论:cemerick

遗憾的是,字节码问题还在我的薪酬等级之上……

以下是一些额外的想法,你可能已经在考虑了

{{assert}}是一个宏,对于其所做的工作来说完全是合理的。问题是,“编译时”在Clojure中是一个复杂的概念:有代码加载时间、AOT编译时间和递归的AOT编译时间。因此,一个部署到生产环境的应用程序中,可能会包含由不同库和命名空间中的{{assert}}使用产生的代码混合体或无代码,这取决于那些库和命名空间的加载、AOT编译和依赖的AOT编译时机,以及每个时间点的{{\*assert\*}}的值。当然,这是所有这种结果依赖于上下文状态宏的案例(我认为这是{{clojure.contrib.logging}}的一个大问题,使得它只能暂时使用log4j)。

JVM断言机制真正吸引人的地方在于,可以根据所需的运行时在每个包的基础上进行参数化。如果想要,将此概念重新实施以使{{assert}}能够对{{\*ns\*}}敏感似乎很简单,但前面提到的编译时复杂性仍然存在,而且有两个独立控制的断言设施的想法听起来并不那么有趣。

我对CLR了解不多,但看起来它不提供像运行时可控断言这样的东西。
0

评论者:stu

我能找到的最有说服力的证据表明,编译器基于{{desiredAssertionStatus}}的返回值设置了一个特殊的类静态最终字段{{$assertionsDisabled}}。HotSpot对此并没有做任何特殊的事情,仅仅是进行死代码消除,让它消失。代码确实以这种方式编译

11: getstatic #6; //字段 $assertionsDisabled:Z
14: ifne 33
17: lload_1
18: lconst_0
19: lcmp
20: ifeq 33
23: new #7; //类 java/lang/AssertionError
26: dup
27: ldc #8; //String X 应该是零
29: invokespecial #9; //方法 java/lang/AssertionError。"":(Ljava/lang/Object;)V
32: athrow

即使在100%确信断言移除是全部的时候,我也会为在Clojure级别上设置一个单独的开关投票,以下是我这样做的原因

  1. 我确实有禁用一些断言的真切和紧急需要,我根本不需要Java互操作。可以说其他人也会处于同样的境地。
  2. 过一段时间后,将会出现多个调试设施,并为Clojure用户方便起见,有一个顶级调试开关。
  3. Java通过命令行标志启用/禁用仍然作为独立的功能是可能的。我们可以在稍后作为对我们断言的小型破坏性更改添加此功能,或者有一个单独的java-assert互操作形式。我对此处于平衡之中。
  4. 我认为从非Java断言形式中抛出{{AssertionError}}是完全合理的。我们不相信静态异常层次的世界,并且断言在生产中的任何情况下都是一个关键失败,无论你叫什么名字。即使是Scala也这样做 :-) http://daily-scala.blogspot.com/2010/03/assert-require-asssume.html

Rich:期待你的祝福以推进这个项目。

0

评论由:richhickey

编译器在静态初始化代码中设置了$assertionsDisabled吗?类加载器有特殊支持吗?你有找到最佳日期证据的链接吗?

0

评论者:stu

  1. 是的,在静态初始化代码中
  2. 根据Brian Goetz(上周私人通信)的说法,类加载器中没有特殊支持。但死亡代码消除做得很好:“编译代码中禁用断言的运行时成本确实应该是零”
0

评论者:stu

链接:谷歌“java assert shirazi”。(不贴链接,因为我在10秒内无法确定是否包含我的会话信息。)

0

评论者:akiel

这个问题有什么新进展吗?我也在寻找在生产环境中禁用断言的便捷方法。

0

评论者:лопуз

我对这个问题的新消息也很感兴趣。
通过 -ea/-da 选项在运行时启用/禁用断言的便捷方式将是一个非常伟大的功能!

0

评论者:лопуз

Btw. 运行时可切换断言的库可以通过 clojars 获取。
https://github.com/pjstadig/assertions
这对我帮助很大。

0
参考:[https://clojure.atlassian.net/browse/CLJ-250](https://clojure.atlassian.net/browse/CLJ-250)(由 stu 报告)
...