Clojure запрещает мутацию локальных переменных ради безопасности потока, но все еще можно писать циклы даже без мутации.При каждом запуске цикла вы хотите, чтобы my-list
имел другое значение, но это также может быть достигнуто с помощью рекурсии:
(let [step (fn [i my-list]
(if (< i 5)
my-list
(recur (inc i) (cons i my-list))))]
(step 0 nil))
Clojure также имеет способ «просто выполнить цикл» безсоздание новой функции, а именно loop
.Он выглядит как let
, но вы также можете перейти к началу его тела, обновить привязки и запустить тело снова с recur
.
(loop [i 0
my-list nil]
(if (< i 5)
my-list
(recur (inc i) (cons i my-list))))
«Обновление» параметров с рекурсивным хвостомВызов может выглядеть очень похоже на изменение переменной, но есть одно важное отличие: когда вы набираете my-list
в своем коде Clojure, его значение всегда всегда будет значением из my-list
.Если вложенная функция закрывается после my-list
, и цикл продолжается до следующей итерации, вложенная функция всегда будет видеть значение, которое my-list
имело при создании вложенной функции.Локальная переменная всегда может быть заменена ее значением, а переменная, которая у вас есть после выполнения рекурсивного вызова, в некотором смысле является другой переменной.
(Компилятор Clojure выполняет оптимизацию, поэтому для его использования не требуется дополнительного пространстваэта «новая переменная»: когда переменная должна быть запомнена, ее значение копируется и когда вызывается recur
, старая переменная используется повторно.)