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. 属性将设置当前根绑定,并添加一个新标志(调试构建将驱动除了断言以外的其他功能。)
  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 传递,因此我不预期这会是一个问题。

然而,我宁愿 * } 默认为 true。

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}}之外的某种异常。这将非常好地与大部分使用{{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 中体现的,并重新审视现有对两者的使用。《不胜任使用断言》文档对此进行了分类讨论,但在不考虑其成本的情况下,在我看来似乎不够现实。

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

0
_所做评论的人:cemerick_

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

以下是一些额外思考,你可能已经在思考这些了

将 assertion 定义为宏在逻辑上完全合理。问题是,在 Clojure 中,“编译时间”是一个复杂的概念:存在代码加载时间、AOT 编译时间和递归 AOT 编译时间。考虑到这一点,在将要部署到生产环境的程序中,包含各种库和命名空间中 assertion 用法产生的代码混合体或没有产生代码是很可能的,这取决于那些库和命名空间何时加载、AOT 编译以及它们的依赖何时 AOT 编译,以及在这些时刻的 {{\*assert\*}} 的值。当然,所有此类依赖于上下文态结果的过程宏也是如此(我认为这是 {{clojure.contrib.logging}} 的一大问题,当时只能与 log4j 一起使用)。

JVM assert 机制真正吸引人的地方是可以针对给定的运行时在包级别上进行参数化,如果需要的话。将这个概念重新实现,使 {{assert}} 能敏感地感知 {{\*ns\*}},似乎很简单,但是之前提到的编译时间复杂性问题依然存在,拥有两个独立控制的 assertion 系统听起来并不有趣。

我对 CLR 几乎一无所知,但它似乎没有提供类似于运行时可控的 assert。
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

即使我们确信 assertion 移除是完整的,我仍然会倾向于为 Clojure 添加一个单独的开关,以下是我的理由

  1. 我有一个切实可行的需求来禁用一些 assertion,我根本不需要 Java 互操作。相当有可能其他人也会处在这个境地。
  2. 随着时间的推移,将会有许多调试工具,对于 Clojure 用户来说,有一个顶级的 debug 开关是方便的。
  3. Java 通过命令行标志启用/禁用仍然可以作为单独的功能实现。以后我们可以将这种(小的)破坏性改变添加到我们的 assert 或有 java-assert 互操作形式。我在这个选择上犹豫不决。
  4. 我相信从非 Java-assertion-form 抛出 {{AssertionError}} 是完全可行的。我们不信仰静态异常层次的世界,而 assertion 在生产中的失败是关键性的,无论你称它为什么。甚至连 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

评论者:lopusz

我也对此问题有任何新的消息感兴趣。
提供一种方便的方法来在运行时启用/禁用断言(最好是使用 -ea/-da 选项)将是非常好的特性!

0

评论者:lopusz

顺便说一下,clojars有可用的库可以对运行时的断言进行切换
https://github.com/pjstadig/assertions
这对我有很大的帮助。

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