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

欢迎!请查看关于页面以了解该工作的更多信息。

0 投票
Clojure
此条目包含两个补丁

# 在clojure.lang.RT加载时根据系统属性clojure.debug设置{{*assert*}}
# 展示assert中的错误信息,包括{{local-bindings&env的新宏)

在批准这些补丁之前需要考虑的事情

# 是否应该有一个简单的方法在Clojure级别上查询是否已启用调试?(检查assert不是一个选项,因为调试最终将推动其他功能)
# 默认情况下断言现在将关闭——这是一个变更!
# 将{{local-bindings}}名称添加到clojure.core中是否很酷?

15 个答案

0 投票

评论者:stu

忽略旧的补丁。考虑以下实现,请审查后将条目移至stu等待

  1. RT将检查系统属性{"clojure.debug"},默认为false
  2. 属性将设置当前根绑定,并添加一个新的flag。(调试构建将推动比断言更多的其他功能。)
  3. Compile.java需要将{"clojure.debug"}作为线程本地绑定推送吗,或者它们是否只能在编译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 传递,因此我不认为这会成为问题。

但是,我 prefers * } 默认保持为真。

0 投票

评论人:cemerick

SS 关于这种方法对 Maven 不会造成任何问题的说法是正确的。此外,构建可以很容易地设置以始终生成两个 jar 文件,一个“正常”,一个“调试”。

我建议,虽然 {{clojure.debug}} 可能会有广泛的影响,但应提供额外的属性,以提供对未来可能出现的每个“调试”相关参数化的细粒度控制。


我想提出一些可能相关的顾虑(在思考上述上下文中的断言之后),其中一些或所有这些都可能是我在各个领域缺乏理解的结果。

观察在 {{core.clj}} 中何处使用了 {{assert}}(据我所知,只有两个地方:验证 {{derive}} 的参数以及检查 {{fn}} 中的预先和后续条件),似乎让它默认为 {{false}} 是不明智的。即,非 {{Named}} 值能够进入层次结构,预先和后续条件将被简单地忽略。

我的理解是,断言(这里指的是 JVM 构造,Clojure 重新使用 {{AssertionError}}),不应该用来验证公共 API 函数的参数,也不应该用来验证函数正常操作的任何方面(即(链接:http://download.oracle.com/javase/1.4.2/docs/guide/lang/assert.html#usage 文本:“何处不使用断言”)。这意味着 {{derive}} 在必要时应该抛出 {{IllegalArugmentException}},而 {{fn}} 的预先和后续条件可能应该抛出 {{IllegalStateException}},或者无论如何,通过 {{assert}} 抛出除 {{AssertionError}} 以外的其他东西。这将与 core 中使用 {{assert-args}} 而不是 {{assert}} 的许多函数相匹配,前者抛出 {{IllegalArgumentException}} 而不是 {{AssertionError}}。

这让我产生了疑问:{{assert}}(以及 * )是否 intended to be a Clojure construct,还是一个准互操作形式?

如果是前者,那么它大致可以具有我们想要的任何语义,但是它似乎不应该抛出 {{AssertionError}}。

如果是后者,那么在 JVM 上抛出 {{AssertionError}} 是合适的,但我们需要确保可以在运行时启用和禁用断言(无需切换不同的 Clojure 构建),理想情况下使用主机定义的开/关选项(例如 {{-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 或前/后条件的变更,最佳实践等是正交和派生的。目前,我们没有在不进行检查的情况下运行的功能。我们需要在编译(调试构建)或运行时(跟踪 -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; //字符串 X应该是零
29: invokespecial #9; //方法 java/lang/AssertionError."":(Ljava/lang/Object;)V
32: athrow

即使我们100%确信断言移除是彻底的,我仍会为以下几个原因投票支持一个独立的Clojure层开关:

  1. 我确实有阻止一些断言的需求,并且我根本不需要Java互操作。可以说其他人也会面临同样的问题。
  2. 随着时间的推移,将会出现多个调试设施,对于Clojure用户来说,在顶层拥有一个<强>debug开关是方便的。
  3. 可以通过命令行标志来启用/禁用Java。我们可以在以后的版本中作为(小)中断变更添加这个功能,或者有一个单独的java-assert互操作形式。我对此持怀疑态度。
  4. 我相信从一个非Java断言形式抛出{{AssertionError}}是非常合适的。我们不相信静态异常层次体系的世界,而且无论你怎么称呼它,生产环境中的断言都是关键失败。甚至Scala也是这样做的!http://daily-scala.blogspot.com/2010/03/assert-require-assume.html

Rich:等待你的祝福来继续进行这项工作。

0 投票

评论人:richhickey

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

0 投票

评论者:stu

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

评论者:stu

链接:Google "java assert shirazi"。 (不发布链接,因为我不能在10秒内判断它是否包含我的会话信息。)

0 投票

评论者:akiel

这个问题有什么新动态吗?我还想找一个方便在生产中禁用断言的方法。

0 投票

评论者:lopuz

我也对这个问题的任何新闻很感兴趣。
在运行时启用/禁用断言的方便方式(最好是使用 -ea/-da 选项)将是一个伟大的功能!

0 投票

评论者:lopuz

顺便一提,有一个支持运行时切换断言的库可以通过 clojars 获得
https://github.com/pjstadig/assertions
这对我帮助很大。

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