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

欢迎!请查看关于页面以获取更多关于如何使用本页面的信息。

0
Clojure
此票据包括两个补丁

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

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

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

15 答案

0

评论者:stu

忽略旧的补丁。请考虑以下实现,然后移动票据以等待 Stu

  1. RT 将检查系统属性 {{"clojure.debug"}}, 默认为 false
  2. 该属性将为当前 的新绑定设置根,还有一个新的 标志。调试构建将会驱动不仅限于 assert 的其他事物。
  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 可能会有广泛的影响,但应该提供额外的属性,以便为可能在未来提供的每个额外的“调试”相关参数化提供精细的控制。


我想提出一些可能的相关关注点(在思考上面的断言之后),其中一些或所有这些可能是由于我在各个领域的理解不足造成的。

看在 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 应该抛出 IllegalArgumentException,而 fn 的预条件和后条件可能应该抛出 IllegalStateException — 或者至少通过 assert 抛出非 AssertionError。这将与 core 中大多数使用 assert-args 而不是 assert 的函数更为匹配,前者抛出 IllegalArgumentException 而不是 AssertionError。

这导致我提出一个问题:assert(和 * })是 clojure 构造,还是准互操作形式?

如果是前者,那么它的大致语义可以是我们想要的任何语义,但似乎不应该抛出 {{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 或前置/后置条件、最佳实践等方面的变化是正交和导出的。目前,我们没有在没有进行检查的情况下运行的功能。我们需要在它们在编译(调试构建)或运行时(track -ea)消失之间进行选择,或者两者兼顾。然后我们可以看看这如何在 assert/pre-post 中反映出来,并重新审视现有的 assert 和 pre-post 的用法。“哪里不使用assertion”文档对其进行了分类处理,但没有考虑其成本,在我看来这似乎不太现实。

如果有人能研究一下Java自身如何生成和优化断言,我将不胜感激。

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; //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 should be zero
29: invokespecial #9; //Method 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-assume.html

Rich:期待你的祝福,以便继续前进。

0
 

评论者:richhickey

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

0

评论者:stu

  1. 是的,在静态初始化代码中
  2. 根据Brian Goetz(上周私密信件)所述,类加载器中没有特殊支持。但是代码删除是很好的:“禁用断言在编译代码中的运行时间成本应该确实为零。”
0

评论者:stu

链接:谷歌搜索 "java assert shirazi"。 (不发布链接,因为无法在10秒内知道它是否包含我的会话信息。)

0

评论者:akiel

关于这个问题有新的进展吗?我还想找到一个在生产环境中禁用断言的方便方法。

0

评论者:lopuz

我对关于这个问题的任何消息都很感兴趣。
通过 -ea/-da 选项在运行时启用/禁用断言的方便方法是伟大的特性!

0

评论者:lopuz

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

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