Ошибка "Возврат только из хвостовой позиции, компиляция" при использовании повторения в цикле

Я очень новичок в Clojure, и на практике я пытаюсь применить простой алгоритм для полуслучайных чисел.

Несколько дней назад я читал о циклах в документах clojure и о том, как они работают с использованием recurя попытался написать цикл с такими строками кода:

(def numbers_semi_random
  (fn []      
    (loop[Xn 4 count 0]                    
     (while (and (not (= Xn m)) (< count m))
       (println (mod (+ (* 5.0 Xn) 7.0) m))
       (recur (mod (+ (* 5.0 Xn) 7.0) m) (inc count))    
))))

Но когда я выполняю код, эта ошибка отображается

CompilerException java.lang.UnsupportedOperationException: Can only recur from tail position, compiling

Что происходит? recur не в хвосте функции?

3 ответа

Решение

Положение хвоста является последней формой, которую необходимо оценить перед тем, как покинуть область. Когда используешь loop, recur оператор должен быть в состоянии начать заново с новыми привязками, дальнейших вычислений из предыдущего цикла не требуется.

В вашем коде у вас есть while блок в хвостовой части loop, Место, куда вы пытаетесь позвонить recur не находится в положении хвоста петли (хотя это находится в хвосте его хвоста).

Возможно, вы предназначены для recur призыв быть вне while? Но сравнение привязок петли Xn а также count к глобальной переменной m в тесте на while заставляет меня думать, что есть еще что-то, что можно распутать здесь. Сколько вложенных циклов вы хотите здесь?

Вполне вероятно, что ваше намерение будет достигнуто с помощью when на месте while (ваш цикл будет продолжать печатать обновленные значения Xn пока условие не выполнено), но для объяснения исключения:

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

Причина этого в том, что while расширяется до loop выражение и поставляет свои собственные recur после предоставленного пользователем тела:

(while test
  body...)

;; expands to

(loop []
  (when test
    body...
    (recur)))

Итак, если вы поставите recur как часть тела whileчто вы получаете

(loop []
  (when test
    ...
    (recur ...) ; from user code
    (recur)))

Здесь пользователь предоставил (recur ...) не находится в положении хвоста относительно его немедленно вмещающего loopи это то, на что жалуется компилятор.

Ваша проблема заключалась в том, что вы использовали "время" вместо "когда". Хотя обычно используется с "do", если вы хотите испускать побочные эффекты

(def n (atom -33)
(while (neg? @n) (do (... something with side effect)) (swap! n inc))

Правильная реализация вашего кода будет...

(def numbers_semi_random
  (fn [m]
    (loop[Xn 4 count 0]
     (when (and (not (= Xn m)) (< count m))
       (println (mod (+ (* 5.0 Xn) 7.0) m))
       (recur (mod (+ (* 5.0 Xn) 7.0) m) (inc count))))))

Придерживайтесь 'когда' или 'если' при использовании рекурсии в Clojure, это намного проще.

Другие вопросы по тегам