我可以在Clojure中混合后置条件和递归函数吗?

时间:2010-11-15 10:56:06

标签: clojure verification tail-recursion

是否可以在同一个Clojure函数中使用recur和post-condition功能?我希望使用后置条件抛出一个异常,但是Clojure似乎试图在重复之后以某种方式包装抛出代码的异常,所以(就像一个愚蠢的例子)这样的函数无法评估。

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

我目前正在使用Clojure 1.3。

4 个答案:

答案 0 :(得分:3)

“recur”就像“转到带有该参数的块的开头”。

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

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

在你的例子中,当x == 1000000时,我希望:post执行一次。

答案 1 :(得分:3)

如果你在https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj#L3905查看defn的实现,你会看到函数的主体被修改,以便尾调用被推出尾部位置。解决这个问题的一种方法是使用辅助函数来调用recur'd函数并将后置条件放在其上:

(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)

答案 2 :(得分: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.

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

答案 3 :(得分:0)

你可以完全做到这一点,并且在很多情况下,获得前/后条件的能力可能值得通过不使用循环/重复特殊形式来减少速度。

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

你可以把它变成一个递归函数而不用吹掉堆栈:

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

(trampoline (countup 0))
1000000

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

跛脚背后的想法是通过让函数的每次迭代都将调用返回到下一个函数而不是直接调用它来避免使堆栈失效。这样就使用了两个堆栈帧(一个用于蹦床,一个用于当前步骤)