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

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

0 投票
Clojure
此票据包括两个补丁

#  当一个补丁在 clojure.lang.RT 加载时根据系统属性 clojure.debug 的存在来设置  {{*assert*}}
# 在 assert 中展开错误信息以包含 {{local-bindings}}{{(一个新的宏,该宏封装了隐式的 <code>&env)}}

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

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

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可能可以进行类似的操作,如果运行时开销可以忽略不计。热Spot有可能会为某些标志的单次检查专门编写省略断言字节码的特殊代码。我只是不确定这个标志是否是(链接:http://download.oracle.com/javase/1.5.0/docs/api/java/lang/Class.html#desiredAssertionStatus() 文本:Class.desiredAssertionStatus)。

这变成assert或前/后条件的更改,最佳实践等是导出的。目前我们没有在不进行检查的情况下运行的功能。我们需要在它们在编译(调试构建)或运行时(跟踪-ear)消失之间进行选择,或者两者都选。然后我们可以看看这如何在assert/pre-post中反映,并重新审视两者现有的使用情况。处理“不应使用断言的地方”的文档将它们归为一类,但在我看来,它没有考虑到它们的成本,似乎不太现实。

如果有人能查看Java本身如何生成和优化assert,我将不胜感激。

0 投票
评论者为:cemerick_

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

以下是一些额外的想法,您可能已经在处理了

{{assert}}作为一个宏对于其作用是完全合理的。问题在于,Clojure中的“编译时”这个概念很复杂:有代码加载时间、AOT编译时间和间接AOT编译时间。因此,对于一个部署到生产环境的应用程序来说,完全可能包含各种库和命名空间产生的代码混合体,这取决于这些库和命名空间何时加载、何时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. 未来将会有多个调试设施,并且有一个顶级的**debug**开关对 Clojure 用户来说很方便。
  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 投票

评论者:лопusz

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

0 投票

评论者:лопusz

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

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