Могу ли я смешать постусловия и рекурсивные функции в Clojure? - PullRequest
2 голосов
/ 15 ноября 2010

Можно ли использовать функции повторения и постусловия в одной и той же функции Clojure? Я надеялся вызвать исключение, используя постусловие, но Clojure, похоже, пытается каким-то образом обернуть код, генерирующий исключение, после повторения, поэтому (как и в случае с глупым примером) такие функции не могут быть оценены.

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

Я сейчас использую Clojure 1.3.

Ответы [ 4 ]

3 голосов
/ 15 ноября 2010

Если вы посмотрите на реализацию defn на https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj#L3905, вы увидите, что тело функции модифицируется так, что хвостовые вызовы выталкиваются из хвостовой позиции. Одним из способов решения этой проблемы является использование вспомогательной функции для вызова функции 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)
3 голосов
/ 15 ноября 2010

«recur» - это как «перейти к началу блока с этими параметрами».

Вы не можете поместить какой-либо код после него, потому что он не сохраняет вещи в стеке, поэтому не знаюоткуда он взялся (и какие проверки он должен выполнить после запуска).

Например, (loop [] (recur)) будет зацикливаться вечно без использования стека.

В вашем примере я ожидаю, что :post будетвыполняется один раз, когда x == 1000000.

1 голос
/ 14 ноября 2011

ИМХО самый простой (без прыжков на батуте или использования вспомогательных функций) будет выглядеть примерно так:

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

Итак, просто добавьте вспомогательный цикл (идентичный сигнатуре функции).

0 голосов
/ 16 ноября 2010

Вы можете сделать это полностью, и во многих случаях возможность получить предварительные / последующие условия может стоить потери скорости, если вы не используете специальную форму loop / recur.

Специальная форма recur не сделает этого для вас, потому что на самом деле это не вызов функции. Вы можете создать свою собственную функцию-оболочку, которая выполняет проверку границ в предварительных и последующих условиях после выполнения всего кода, или вы можете использовать встроенную функцию trampoline, сэкономить немного усилий и проверять условия на каждой итерации (вам нужно решить, хотите ли вы этого)

Вы можете превратить это в рекурсивную функцию без перебора стека:

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

(trampoline (countup 0))
1000000

Это изменяет условие публикации, чтобы игнорировать промежуточные случаи (где он возвращает функцию для запуска далее) и проверять только конечный результат.

Идея, лежащая в основе trampolining, состоит в том, чтобы избежать перебора стека, заставляя каждую итерацию функции возвращать вызов следующей функции, а не вызывать ее напрямую. таким образом используются два кадра стека (один для батута и один для текущего шага)

...