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

欢迎!请查看 关于 页面,了解更多关于这个平台的信息。

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.langUnsupportedOperationException: 仅可以从尾部位置递归

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

14 个回答

0

[email protected] 发布的评论

宏展开显示,主体被放置在 let 形式中以捕获结果,稍后用于使用 post 条件进行测试,但递归不再有合适的目标。一旦了解发生了什么,使用循环形式的解决方案就很容易了,但这是一个令人惊讶的限制。

0

[email protected] 发布的评论

在主体周围使用局部 fn* 并使用原始参数来调用它,以便递归有合适的目标。更新:对于处理解构来说还不够好。补丁已撤回。

0
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 提出

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

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

请勿将 "patch" 用作标签,该标签是 Patch 字段的作用。有关好标签和坏标签的列表,请参阅 http://dev.clojure.org/display/community/Creating Tickets

0

[email protected] 发布的评论

知识渊博的评论者可以查看 CLJ-701,以防其适用于拟议的补丁。

0

评论人:hiredman

关于 clj-701 的回复

将循环表达式语义表示为 jvm 字节码是比较棘手的,因此编译器 kind of punts,将循环表达式提升到立刻调用的匿名函数中,关闭任何在循环中所需的局部作用域的内容,这有一些问题,比如 CLJ-701 中看到的问题,丢失 clojure 编译器不跨越函数跟踪的类型数据,函数对象的额外分配(jit 可能能很好地处理这个问题,我不确定)等。

clj-701 和此票据的世界相撞的地方是,此票据上的补丁将函数体提升为循环表达式,如果没有 clj-701 的补丁,将会有上面提到的问题,但我们已经在任何难以用字节码表示的表达式(循环尝试等)用作表达式的地方遇到这些问题,也许这无关紧要,或者 clj-701 将以某种方式修复来减轻这些问题。

一般思考

人们似乎喜欢断言的一个特性是可以禁用在生产环境中(我从未真正看到有人用Clojure这样操作过),断言以及:pre/:post有这个能力(可能仅在宏展开时起作用,我不太记得了),由于循环提升可能会影响性能,可能有一个机制来禁用循环提升会更好(可能使用断言相同的标志?)。

0
...