请分享您的想法,参加2024年Clojure调查!

欢迎!请参阅关于页面,了解该功能的更多信息。

0
编译器
Michael O'Keefe <[email protected]> 在邮件列表上发布了一个示例代码,该代码只有在添加了:后置条件之后才会导致编译器错误。以下是经过我稍微修改的版本:


(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形式中以捕获结果,稍后与后置条件进行测试,但是循环不再具有正确的目标。一旦您了解发生了什么,使用循环形式的对此的解决方案就很简单,但这是一个令人惊讶的限制。

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 会像下面的 case 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 留言

Patch 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 做这件事),断言和 :pre/:post 有一些这样的能力(它可能在宏展开时才起作用,我不记得了),由于提升循环可能会影响性能,可能需要有一种机制来禁用它(也许使用与断言相同的标志)?

0
...