2024 年 Clojure 发展状况调查中分享您的看法!

欢迎!请参阅关于页面以获取更多关于如何操作的信息。

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

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

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

# 是否应该有一种简单的 Clojure 级别的方式来查询是否启用了调试?(检查 assert 并不相同,因为调试最终应该驱动其他功能)
# 断言现在默认关闭 -- 这是一个变化!
# 给 clojure.core 添加{{local-bindings}}这个名字合适吗?

15 答案

0 投票

评论由:stu 提出

忽略旧的补丁。考虑以下实现,请审核后,将问题单移动到 Stu 的等待阶段

  1. RT 将检查系统属性{{{"clojure.debug"}}},默认值为 false
  2. 属性将设置当前 的根绑定,并添加一个新的 标志。(调试构建不仅会驱动断言,还会驱动其他东西。)
  3. 在编译 clojure 时,Compile.java 是否需要将 } 或 } 推送到线程局部绑定,还是可以仅在编译 clojure 时将其作为根绑定?
  4. 将添加{{* }绑定到{{clojure.main/with-bindings}}。其他地方需要吗?
  5. build.xml 不需要改变 -- 系统属性将通过(而且 build.xml 本来就可能不会使用很长时间了)
  6. 一旦我们达成一致的方法,我将ping Maven插件和Lein所有者,以便他们将设置流程通过。
  7. 更好的断言消息将是一个单独的工单。
  8. **debug** 和 **unchecked-math** 之间的交互是什么?更改检查到 }}?
0 投票

评论者:richhickey

#3 - 仅根绑定
#4 - 不应该在其中**debug** 和 **assert**,原因与#3相同 - 我们不希望人们设置!
#8 - 是的,用辅助函数包裹它

#6 - 我最大的保留意见是,这还没有根据Maven最佳实践来制定

0 投票

评论者:stuart.sierra

系统属性可以通过Maven传递,所以我预计这不会成为问题。

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

0 投票

评论者:cemerick

SS关于这个方法对Maven没有问题。此外,构建可以很容易地配置以为总是发出两个jar,一个"正常",一个"debug"。

我建议,尽管 {{clojure.debug}} 可能会有广泛的影响,但还应该提供额外的属性,以便对将来可能可用的每个额外的"debug"-相关参数化提供精细控制。


我想提出一些建议性的关注点(在现在考虑了上面的断言后),其中一些或所有可能是由于我在某些领域的理解不足所导致的。

由于{{assert}}在{{core.clj}}中的使用情况来看(据我所知,只有两个地方:验证{{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}}是合适的,但我们需要注意assertions可以在运行时打开或关闭(而无需在不同构建的Clojure之间切换),理想情况下使用宿主定义的开关(例如,{{-ea}}及其相关开关),很可能不是像*这样的东西。我不知道目前这是否可能或实用(我推测这可能需要非微不足道的编译器更改)。


希望到目前为止以上内容还没有成为过眼云烟。提前感谢您的耐心。:-)

0 投票

评论者:richhickey

感谢您的宝贵意见Chas。目前还没有得出结论。我认为我们应该回顾一下这里的目标,然后再继续寻找解决方案。作为一个动态语言,有许多关于我们的程序我们可能想要验证的事情,而检查的成本是我们不愿意在生产中付出的。

作为一个宏,assert有这样一个很好的属性,如果**assert**在编译期间不为真,它会生成nil,根本不会有条件测试。因此,现在它更像是静态条件编译。

Java assert确实可以通过-ear作为您所说的在运行时切换。我还没有查看Java asserts生成的字节码,但Clojure assert可能做类似的事情,如果运行开销可以忽略不计。HotSpot很可能针对检查某些标志值的单个检查有一段特殊的代码来省略断言字节码。我不确定这个标志是否(链接:http://download.oracle.com/javase/1.5.0/docs/api/java/lang/Class.html#desiredAssertionStatus() 文字:Class.desiredAssertionStatus)。

这变成assert或预/后条件、最佳实践等更改,是正交和衍生的。目前我们没有在不进行检查的情况下运行的设施。我们需要在它们在编译(调试构建)或在运行时(track -ea)消失之间进行选择,或者在编译和运行时都消失。然后我们可以看看 cómo 这样做反映在assert/pre-post中,并重新检查现有两种使用的情况。“不要使用断言的地方”文档从范围上处理了它们,但在没有考虑它们的成本的情况下,在我看来这似乎是不切实际的。

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

0 投票
由:cemerick_发表的评论

很遗憾,字节码问题超出了我的预算范围……

以下是一些额外的想法,您可能已经在考虑中

{{assert}}作为宏对其所完成的功能来说是完全有意义的。问题是,“编译时”在Clojure中是一个棘手的理念:有代码加载时间、AOT编译时间以及递归AOT编译时间。考虑到这些,一个应用在部署到生产环境后,其代码或uisine可能会变得复杂或没有代码,这是因为各个库和命名空间在使用{{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. 在一段时间内,将会有多个调试工具,而顶级有debug开关对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 提出

链接:在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](https://clojure.atlassian.net/browse/CLJ-250)(由 stu 报告)
...