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 形式中,以便稍后与后置条件一起进行测试,但 recur 已经不再有适当的目标。一旦理解了发生的事情,使用循环形式的解决方案就很简单了,但这是一个令人惊讶的限制。

0
jira

评论者:[email protected]

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

0
jira
0

评论者:[email protected]

补丁被撤回,因为它在解构参数上会出错。

0

评论者:[email protected]

在处理补丁时,我遇到了一个相关的问题::pre 条件是否应用于每个 recur "调用"。最初,我认为 :pre 条件只需在初始函数调用时检查一次,而 never 在 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]_

关于史蒂夫对解读 :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

Patch 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

re clj-701

在 JVM 字节码中表达循环表达式语义比较棘手,因此编译器有点逃避,将表达式循环提升到立即执行的匿名函数中,关闭作用于循环所必需的任何作用域内的内容,这会产生一些问题,如 CLJ-701 中看到的问题,类型数据的丢失,clojure 编译器不跟踪函数之间的类型数据,函数对象的额外分配(jit 可能会很好地处理这些,我不太确定)等。

CLJ-701 和此票据的世界相交的地方是这个票据上的补丁将函数体提升出来作为循环表达式,如果没有 CLJ-701 中的补丁,它将会有我上面列出的问题,但我们已经有这些问题了,任何难以在字节码中作为表达式表示的东西(尝试循环)都用作表达式,这可能无关紧要,或者 CLJ-701 将以某种方式修复以减轻这些问题。

一般思考

人们从断言(asserts)中喜欢的一个特性似乎是可以将它们在生产环境中禁用(我从未真正看到有人用 Clojure 干这个),断言和 :pre/:post 有一点点做这个的能力(可能只是在宏展开时生效,我不太记得了)。由于循环提升可能会影响性能,可能需要有一种机制来禁用它(可能使用断言相同的标志?)。

0
by
...