3

Is it possible to use both recur and post-condition functionality in the same Clojure function? I was hoping to throw an exception using the post-condition, but Clojure appears to be trying to wrap the exception throwing code after the recur somehow, so (just as a stupid example) functions like this cannot be evaluated.

(defn countup [x]
  {:pre [(>= x 0)]
   :post [(>= % 0)]}
  (if (< x 1000000)
    (recur (inc x))
    x))

I'm using Clojure 1.3 at the moment.

4

4 回答 4

4

如果您查看https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj#L3905defn的实现,您会看到函数的主体被修改,因此尾巴呼叫被推出尾部位置。解决这个问题的一种方法是使用辅助函数来调用递归函数并将后置条件放在它上面:

(defn- countup* [x]
  (if (< x 1000000)
    (recur (inc x))
    x))

(defn countup [x]
  {:pre [(>= x 0)]
   :post [(>= % 0)]}
  (countup* x))

(countup 999999)
;=> 1000000

(countup -1)
; Assert failed: (>= x 0)
于 2010-11-15T15:07:15.607 回答
3

“recur”就像“使用该参数转到块的开头”。

你不能在它之后放置任何代码,因为它不会将东西保存在堆栈中,所以不知道它来自哪里(以及它应该在运行后执行哪些检查)。

例如,(loop [] (recur))将永远循环而不消耗堆栈。

在您的示例中,我希望:post在 x==1000000 时执行一次。

于 2010-11-15T11:32:42.277 回答
1

恕我直言,最简单的(没有蹦床或使用辅助功能)将是这样的:

(defn countup [x]
  {:pre [(>= x 0)]
  :post [(>= % 0)]}
  (loop [x x]
        (if (< x 1000000)
            (recur (inc x))
          x)))

(countup 999999)
1000000

(countup -1)
; Evaluation aborted.

因此,只需注入一个辅助循环(与函数签名相同)。

于 2011-11-14T11:46:18.987 回答
1

您完全可以做到这一点,并且在许多情况下,获得前置/后置条件的能力可能值得因不使用循环/递归特殊形式而损失的速度。

recur 特殊形式不会为你做,因为它不是真正的函数调用。您可以创建自己的包装函数,在所有代码运行后检查前后条件的边界,或者您可以使用内置trampoline函数并节省一点精力并在每次迭代时检查条件(您需要决定是否你想要那个)

您可以将其转换为递归函数,而不会破坏堆栈:

(defn countup [x]
              {:pre [(>= x 0)]
              :post [(or (ifn? %) (>= % 0))]}
              (if (< x 1000000)
                  #(countup (inc x))
                  x))

(trampoline (countup 0))
1000000

这将更改后置条件以忽略中间情况(它返回下一个运行的函数)并仅验证最终结果。

蹦床背后的想法是通过让函数的每次迭代将调用返回到下一个函数而不是直接调用它来避免炸毁堆栈。这样一来,两个堆栈框架被使用(一个用于蹦床,一个用于当前步骤)

于 2010-11-16T00:49:42.087 回答