请在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.lang.UnsupportedOperationException: 只有从尾部位置递归

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

14 答案

0

评论者:[email protected]

宏展开显示,代码体被放在了一个let形式中,以捕获结果以便 later 测试 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] 发表的评论_

我对这个缺陷放弃了。我的方法为处理边缘情况添加了太多的复杂性。我建议遇到这个问题的每个人都尝试使用 "循环" 的解决方案。


(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”作为标签 - 那是补丁字段的目的。更多关于良好和不良标签的信息请参阅http://dev.clojure.org/display/community/Creating Tickets

0

评论者:[email protected]

更了解情况的评论者可以查看 CLJ-701,以防它适用于提出的补丁。

0

评论者:hiredman

恢复 clj-701

在 JVM 字节码中表达循环表达式语义很棘手,因此编译器多少有点逃避问题,将表达式循环提升到立即调用的匿名函数中,封闭循环所需要作用域内的任何内容,这有一些问题,就像 CLJ-701 中看到的那样,丢失简历程序员不跟踪跨函数的类型数据,函数对象额外的分配(jit 可能处理得很好,我不确定)等。

clj-701 和这个条目世界相交的地方是这个条目上的补丁将函数体提升为循环表达式,如果没有 clj-701 的补丁,将会有上述问题,但我们已经在任何难以作为表达式在字节码中表达的东西(尝试循环)作为表达式使用时遇到过这些问题,也许这无关紧要,也许 clj-701 将以某种方式修复来解决这些问题。

一般思考

人们似乎喜欢从断言中获取的一个特性是能够在生产环境中禁用它们(我从未见过有人用Clojure来做这个),断言和:pre:/:后部分具有一定的实现这样的功能(这可能在宏展开时才能生效,我不记得了),由于循环的提升可能会影响性能,所以有一个机制禁用它可能会很不错(也许可以使用断言相同的标志?)。

0
...