请分享您的想法,参加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))


编译异常java.lang.UnsupportedOperationException: 只能从尾部递归

解决方案是将代码块包裹在一个循环中,简单地重新绑定原始参数。

14 答案

0

评论由:[email protected]发布

宏展开显示主体被放置在let表单中,用于捕获结果以便与post条件后来测试,但递归不再有合适的目标。一旦理解了正在发生什么,使用循环形式的解决方案就简单了,但它是一个出乎意料的限制。

0

评论由:[email protected]发布

使用局部fn*围绕主体,并用原始参数调用它,以便recur有一个合适的目标。更新:不足以处理解构。补丁已撤回。

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

添加处理 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

评论由: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 中看到的那样,类型数据的丢失(Clojure 编译器不在函数间跟踪类型数据),函数对象的额外分配(Jit 可能处理得很好,我不确定)等。

clj-701 和此请求的世界相交的地方是该请求的补丁将函数体提升为循环表达式,如果没有 clj-701 的补丁,将出现上述问题,但我们已经在任何难以在字节码中以表达式(比如 loop 和 try)使用的表达式的地方遇到这些问题,也许这不重要,或者 clj-701 将以某种方式修复以减轻这些问题。

一些思考

人们似乎很喜欢 assert 的一个特性是可以将其在试用期禁用(我从未真正看到有人实际在 clojure 中这样做),assert 和 :pre/:post 具有几项这样的能力(这可能在宏扩展时才有效,我不记得了),因为提升循环可能会影响性能,可能有一个机制可以禁用它(也许使用 assert 的相同标志?)。

0
by
...