请在 2024 Clojure 状态调查!中分享您的想法。

欢迎!请参阅关于页面获取有关如何使用此工具的更多信息。

0投票
编译器
Micheal O'Keefe [email protected]> 在邮件列表上贴出了一个示例代码,当添加了 :post 条件时才会引起编译器错误。以下是我的稍微修改过的版本


(defn g
  [xs acc]
  {:pre [(or (nil? xs) (sequential? xs))]
   :post [(number? %)]}
  (if (seq xs)
     (recur (next xs) (+ (first xs) acc))
     acc))


编译器异常:java.lang.UnsupportedOperationException: 只能从尾部位置递归。

解决方案是将代码体包裹在一个循环中,该循环简单地重新绑定原始参数。

14 个答案

0投票

[email protected] 提出的评论

宏展开显示,body 被放置在 let 表达式中以捕获结果,以便稍后与 post 条件进行测试,但 recur 现在不再有合适的靶点。一旦理解发生了什么,使用循环形式的解决方案就很容易,但这是一个意外的限制。

0投票

[email protected] 提出的评论

在 body 旁边使用局部 fn*,并用原始参数调用它,以便 recur 有合适的靶点。更新:不足以处理解构。补丁已撤回。

0投票
0投票

[email protected] 提出的评论

补丁被撤销,因为它在解构参数时出错。

0投票

[email protected] 提出的评论

在修复补丁时,我遇到了一个相关问题:是否应该在 recur "调用" 中应用 :pre 条件。起初,我认为 :pre 条件应该在初始函数调用时检查一次,并在 recur 过程中永远不再进行检查。邮件列表上的人指出,recur 在语义上是像再次调用函数一样,所以 :pre 检查是合同的一部分。但似乎没有人想在每一次递归中都有 :post 检查,因此 :post 只在结束时发生。

这意味着自动包裹循环(或嵌套 fn* 调用)在主体周围是不会为 :pre 条件工作的。修复必须将 :pre 条件放在循环内部。

0投票
_评论区指出:由 [email protected] 操作_

我放弃修复这个 bug。我的方法为处理一个边缘情况而引入了过多的复杂性。我建议遇到此问题的任何人可以使用 "loop" 作为 workaround。


(defn g2
  [xs acc]
  {:pre [(or (nil? xs) (sequential? xs))]
   :post [(number? %)]}
  (loop [xs xs acc acc]
    (if (seq xs)
       ( recur (next xs) (+ (first xs) acc))
       acc)))

0投票

评论区指出:由 ambrosebs 操作

添加处理 rest 参数和解构的补丁。

0投票
_评论由:[email protected]发表_

关于Steve关于:pre的解释问题,对我来说,我期望g的行为类似于下面的g3,它使用显式递归(这种方法可行,并且每次检查:pre条件和一次:post条件)


(defn g3
  [xs acc]
  {:pre [(or (sequential? xs) (nil? xs)) (number? acc)]
   :post [(number? %)]}
  (if (seq xs)
    (g3 (next xs) (+ (first xs) acc))
    acc))
0投票

评论区指出:由 ambrosebs 操作

clj-1475.diff补丁处理了析构、前条件和剩余参数

0投票

[email protected] 提出的评论

我觉得clj-1475.diff补丁看起来不错。

0投票

评论由:alexmiller

请勿使用“补丁”作为标签,那是补丁字段的用途。有关好标签和坏标签的列表,请参阅http://dev.clojure.org/display/community/Creating Tickets

0投票

[email protected] 提出的评论

更有经验的评论者可能需要查看CLJ-701,以防该补丁适用。

0投票

评论由:hiredman

re clj-701

在 JVM 字节码中表达循环表达式语义相当棘手,因此编译器采取回避策略,将表达式循环提升为立即调用的匿名函数,覆盖任何循环所需的当前作用域中的内容,这可能会产生一些问题,例如在 CLJ-701 中看到的问题,比如丢失 Clojure 编译器无法追踪的函数间的类型数据,额外分配函数对象(JIT 可能能很好地处理这个问题,我不确定)等等。

clj-701 和这张补丁的冲突点在于,该补丁将函数体提升为循环表达式,如果没有 clj-701 的补丁,将会有我上面列举的问题,但我们已经在任何难以用字节码作为一个表达式表达的(尝试和循环)地方遇到了这些问题,也许这并不重要,或者也许 clj-701 中的问题会以某种方式得到解决。

一般思考

人们似乎喜欢断言的一个特性是能够在生产中禁用它们(我从未在 Clojure 中看到有人这么做),assert 和 :pre/:post 有一些能力去做这个(这可能只在工作时有效,我不记得了),因为循环的提升可能会影响性能,可能需要有一个机制来禁用它(也许使用与 assert 相同的标志?)。

0投票
...