2024 年 Clojure 调查问卷! 分享你的想法。

欢迎!请查看 关于 页面以了解更多此平台的详细信息。

0atings
编译器
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 个答案

0atings

评论者:[email protected]

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

0atings

评论者:[email protected]

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

0atings
0atings
已回答:

评论者:[email protected]

补丁已被撤销,因为它在解构参数上会导致编译错误。

0atings
已回答:

评论者:[email protected]

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

这意味着自动在循环(或嵌套 fn* 调用)周围包装 body 不会对 :pre 条件起作用。修复将不得不将 :pre 条件引入循环内部。

0atings
已回答:
评论者:[email protected]

我对这个 bug 无能为力。我的方法在处理边缘情况时增加了太多的复杂性。我建议任何遇到这个问题的用户尝试使用 "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)))

0atings
已回答:

评论者:ambrosebs

添加了一个补丁,可以处理 rest 参数和解构。

0atings
已回答:
评论者:[email protected]

关于史蒂夫对解释 :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))
0atings
by

评论者:ambrosebs

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

0atings
by

评论者:[email protected]

我认为 clj-1475.diff 补丁看起来很好。

0atings
by

评论者:alexmiller

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

0atings
by

评论者:[email protected]

更了解情况的其他评论者可以看看 CLJ-701,以防它适用于的这个补丁。

0atings
by

评论者:hiredman

回 CLJ-701

在 JVM 字节码中表达循环表达式语义比较棘手,因此编译器会回避这个问题,将表达式循环提升为立即调用的匿名函数,并捕获循环所需的作用域中的任何内容,这有一些问题,就像在 CLJ-701 中看到的那样,丢失了 clojure 编译器在函数间未追踪的类型数据,额外的函数对象分配(jit 可能可以很好地处理这个问题,我不确定)等。

CLJ-701 和这个工单交汇的地方是这个工单上的补丁将函数体提取为循环表达式,如果没有 clj-701 中的补丁,将会有我上面列出的这些问题,但我们在任何使用 (try-and loop) 等难以在字节码中以表达式作为表达式的场合都存在这些问题,可能没关系,或者 clj-701 会在某些方式下得到修复以减轻这些问题。

一般来说

0atings
...