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

欢迎!有关如何工作的更多信息,请参阅关于页面。

0
编译器
Michael 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))


CompilerException java.lang.UnsupportedOperationException: 仅能从尾部位置调用 recur

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

14 个答案

0

由:[email protected] 发表的评论

宏展开显示,主体被放置在 let 形式中以捕获结果以供后续与 post 条件测试,但 recur 已经没有正确的目标。一旦理解了发生了什么,使用循环形式的解决方案就很容易,但这是一个意外的限制。

0

由:[email protected] 发表的评论

在主体周围使用具有本地 fn* 的 fn,然后用原始参数调用它,这样 recur 就有一个正确的目标。更新:对于处理析构来说还不够好。补丁已撤回。

0

由:[email protected] 发表的评论

链接到原始主题讨论: https://groups.google.com/d/topic/clojure/Wb1Nub6wVUw/discussion

0

由:[email protected] 发表的评论

补丁被撤回,因为它会在解构参数上出错。

0

由:[email protected] 发表的评论

在处理补丁时,我遇到了一个相关的问题::pre 条件是否应该应用于每个 recur "调用"。最初,我认为 :pre 条件只需在初始函数调用时检查一次,而在 recur 中永远不会检查。邮件列表上的人指出,recur 在语义上就像是再次调用函数,所以 :pre 检查是合同的一部分。但似乎没有人希望对每次递归进行 :post 检查,所以 :post 只会在最后发生。

这意味着自动在体周围包装循环(或嵌套 fn* 调用)不会对 :pre 条件有效。修复需要将 :pre 条件引入循环中。

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

我放弃对此错误的修复。我的方法为处理一个边缘情况添加了太多的复杂性。我建议任何遇到此问题的使用者尝试 "loop" 修复方案。


(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
by

由:ambrosebs 发表评论

Patch clj-1475.diff 处理了解构、预条件和余数参数

0
by

由:[email protected] 发表的评论

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

0
by

评论者:alexmiller

请勿将 "patch" 作为标签 - 该字段就是用来表示补丁的。有关良好和不良标签的列表请参阅 http://dev.clojure.org/display/community/Creating Tickets

0
by

由:[email protected] 发表的评论

更 knowledgeable 的评论者可能需要查看 CLJ-701,以防它适用于这个补丁。

0
by

评论者:hiredman

关于 clj-701 的评论

由于在 JVM 字节码中表示循环表达式的语义复杂,编译器有点退缩,将表达式循环提升到立即调用的匿名函数中,关闭所需的作用域中的内容,这导致了一些问题,如 CLJ-701 中看到的问题,丢失了 clojure 编译器不跟踪跨函数的类型数据,额外的函数对象分配(JIT 可能会很好地处理这个问题,我不确定)等等。

clj-701 和这个工单的世界冲突在于,这个工单中的补丁将函数体提高为循环表达式,如果没有 clj-701 中的补丁,将会有我上面列出的问题,但我们已经在任何难以在字节码中以表达式形式表示(尝试和使用循环)的地方遇到了这些问题,也许这无关紧要,也许 clj-701 将通过某种方式得到修复以缓解这些问题。

一般感想

人们似乎喜欢assert的一个特性是能够于生产环境中禁用它(我从未真正看到有人用Clojure这样做过),assert和:pre/:post有一些禁用功能(也许只有在宏展开时才生效,我不记得了)因为循环提升可能会影响性能,所以有一个禁用它的机制可能很不错(也许使用和assert相同的标志?)。

0
...