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

欢迎!请访问关于页面以获取更多关于这个怎么做的工作信息。

0
Clojure
此问题包括两个补丁

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

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

# 是否应该有一个简单的方法来查询是否启用了调试?(检查assert不是同一回事,因为调试最终会驱动其他功能)
# 默认情况下断言将关闭 - 这是一个变化!
# 将名称 {{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
by

评论者:richhickey

#3 - 只有根绑定
#4 - 不应该在 not 与 #3 相同的理由下存在于 with-bindings - 我们不想让人们对 **debug** 或 **assert** 进行设置!
#8 - 是的,将其封装在辅助函数中

#6 - 我最大的保留意见是这还没有受到 Maven 最佳实践的启发

0
by

评论者:stuart.sierra

系统属性可以通过 Maven 传递,因此我不期望这会成问题。

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

0
by

评论者: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}},而函数的前置条件和后置条件可能应该抛出{{IllegalStateException}}——或者无论如何,通过{{assert}}抛出除了{{AssertionError}}之外的其他异常。这将与核心中使用{{assert-args}}而不是{{assert}}的大多数函数的语义更加吻合,前者的作用是抛出{{IllegalArgumentException}}而不是{{AssertionError}}。

那么,问题来了:{{assert}}(以及* *)是否被认为是Clojure的构造或伪互操作形式?

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

如果是后者,那么在JVM中{{AssertionError}}是适当的,但我们需要注意,断言应该在运行时能够启用和禁用(而无需在不同的Clojure构建之间切换),理想情况下,使用宿主定义的开关(例如 {{-ea}} 和朋友),很可能不是像 * 。我不知道这是否可能或可行(我假设这需要非平凡的编译器更改)。


希望上述内容现在不会成为过去的隐患。提前感谢您的耐心。;-)

0

评论者:richhickey

感谢你给出的有用意见,Chas。还没有得出结论。我认为我们应该后退一步,看看目标是什么,然后再继续着手解决问题。作为一个动态语言,我们可能想要验证程序中的许多事情,而检查的成本是我们不愿意在生产环境中付出的。

断言作为一个宏,有这样一个优点:如果**断言**在编译期间不为真,它将生成nil,根本不进行条件测试。因此,现在它更像是一种静态条件编译。

Java断言确实具有运行时切换能力,通过- ea,正如你说的。我还没有查看Java断言生成的字节码,但如果运行时开销微不足道,Clojure断言可能也可以执行类似操作。HotSpot很可能为的单个检查某些标志而跳过断言字节码具有特殊的代码。我只是不确定这个标志是(链接:http://download.oracle.com/javase/1.5.0/docs/api/java/lang/Class.html#desiredAssertionStatus() 文本:Class.desiredAssertionStatus()).

这导致对断言或前置/后置条件以及最佳实践等的变化是正交的。目前我们还没有在没有检查的情况下运行的功能。我们需要在它们在编译时(调试构建)或运行时(跟踪 -ea)消失或在两者之间做出选择。然后我们可以看看这如何在断言/前置/后置中得到反映,并重新审视现有的断言使用。关于“不使用断言的地方”的文档对它们进行了分类处理,但在没有考虑到它们的成本方面,在我看来似乎不切实际。

我非常感谢如果有人能查看Java如何生成和优化断言。

0
评论由:cemerick_发表

不幸的是,字节码问题一直超出我的能力范围……

以下是一些额外的想法供您参考,或许您已经有了

{{assert}} 作为宏,它在所做的工作中非常有意义。然而,在 Clojure 中,“编译时间”是一个复杂的概念:有代码加载时间、AOT 编译时间和传递性 AOT 编译时间。鉴于这一点,在生产环境中部署的应用程序,其中包含根据库和命名空间加载、AOT 编译或其依赖项 AOT 编译的时间而产生或未产生 {{assert}} 的各种代码混合体,以及在每个时间点的 {{\*assert\*}} 的值,这是完全可能的。当然,这种情况适用于所有这样的宏,其结果依赖于上下文相关的状态(我认为这是 {{clojure.contrib.logging}} 很大的问题,它一段时间内只能与 log4j 配合使用)。

JVM 断言机制真正吸引人的地方在于,如果需要,它可以根据给定的运行时对每个包进行参数化。将这个概念重新实现,以便 {{assert}} 可以对 {{\*ns\*}} 敏感,这似乎很简单,但前面提到的编译时间复杂度仍然存在,有两个独立控制的断言设施的想法听起来并不令人愉快。

我对 CLR 几乎一无所知,但它似乎没有提供类似运行时可控断言的东西。
0

评论由:stu发表

我所能找到的最佳(过时)证据表明,编译器会根据 {{desiredAssertionStatus}} 的返回值设置一个特殊的类静态最终字段 {{$assertionsDisabled}}。HotSpot 对此没有做任何特殊处理,只是简单地消除死代码。代码的确以此方式编译

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

即使我们100%确信断言移除是完全的,我仍会支持一个单独的 Clojure 级别的开关,以下是一些原因

  1. 我确实需要禁用一些断言,我根本不需要Java互操作。有争议地说,其他人可能也会处于同样的境地。
  2. 随着时间的推移,会有多个调试设施,对于 Clojure 用户而言,有一个顶级 debug 开关很方便。
  3. Java 通过命令行标志启用/禁用仍然是一个单独的功能。我们可以稍后将其作为对我们 assert 的(小的)破坏性更改来添加,或者有一个单独的 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

评论者:лопуз

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

0

评论者:лопуз

顺便说一下,clojars提供了一个运行时开关断言的库。
https://github.com/pjstadig/assertions
这一点大大帮助了我。

0
...