请在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形式中,以捕获结果以供以后与后置条件一起测试,但recur不再有一个适当的目标。一旦您理解了发生了什么,使用循环形式的解决方案就很容易了,但这是一个令人惊讶的限制。

0

评论者:[email protected]

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

0
0

评论者:[email protected]

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

0

评论者:[email protected]

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

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

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 会在某些方面得到修复以减轻这些问题。

一般思考

好像人们喜欢断言的特性之一是能够在生产环境中禁用它们(我从未看到有人用 clojure 这样做过),assert 和 :pre/:post 有一些能力做到这一点(这可能在宏展开时才有效,我不记得了),因为提升循环可能会影响性能,可能需要一个机制来禁用它(可能使用 assert 的同一个标志?)。

0
...