7.4 KiB
title, localeTitle
| title | localeTitle |
|---|---|
| Clojure Looprecur | Clojure Looprecur |
Возможно, вам придется понять, if и let полностью понять рекурсию в Clojure.
for и while
Clojure не имеет циклов или циклов. Это имеет смысл, если вы думаете об этом. Цикл for изменяет переменную, и это не допускается в Clojure.
for (var i = 0; i < 10; i++) {
console.log(i);
}
i++ означает, что мы добавляем его к переменной i каждый раз, когда цикл завершается - ясный пример изменяемой переменной.
while циклы менее явно зависят от изменения переменных, но они, как и для циклов.
var i = 0;
while (i < 10) {
console.log(i);
i++;
}
while циклов всегда есть условие, такое как i < 10 , и будет ломаться, если это условие перестает быть истинным. Это означает, что у них должен быть какой-то побочный эффект (например, добавление 1 к i ), чтобы условие в конечном итоге было ложным; в противном случае цикл будет длиться вечно.
Рекурсия
К счастью, у Clojure есть одна петля. Эти циклы используют рекурсию - функцию, которая вызывает себя. Простейшим рекурсивным алгоритмом является поиск положительного числа factorial (5 факториалов, например, 5 * 4 * 3 * 2 ).
(defn fact [x]
(loop [nx prod 1] ;; this works just like a 'let' binding.
(if (= 1 n) ;; this is the base case.
prod
(recur (dec n) (* prod n)))))
Вы заметите, что (loop [nx prod 1] ...) выглядит очень похоже на привязку let . Фактически это работает точно так же - здесь мы привязываем n к x и prod 1.
Каждая рекурсивная функция имеет «базовый регистр». Это условие, которое делает цикл остановки цикла. В этом случае наш цикл останавливается, если n = 1 , и возвращает prod . Если n не равно 1, то цикл повторяется.
(recur (dec n) (* prod n))
Эта recur функция перезапускает цикл, но с разными привязками. На этот раз n не привязано к x , а привязано к (dec n) (что означает decrement n или n - 1 ), а prod привязан к (* prod n) .
Поэтому, когда мы вызываем функцию, это происходит:
(fact 5)
; Loop 1: 5 != 1, so the loop recurs with 4 (5 - 1) and 5 (1 * 5).
; Loop 2: 4 != 1, so the loop recurs with 3 (4 - 1) and 20 (5 * 4).
; Loop 3: 3 != 1, so the loop recurs with 2 (3 - 1) and 60 (20 * 3).
; Loop 4: 2 != 1, so the loop recurs with 1 (2 - 1) and 120 (60 * 2).
; Loop 5: 1 == 1, so the function returns prod, which is now equal to 120.
; => 120
Гениальная вещь о рекурсии состоит в том, что сами переменные никогда не меняются. Единственное, что меняется, это то, о чем говорят n и prod . Мы никогда не говорим, что n-- , или n += 2 .
Зачем использовать loop / recur?
Вам может быть интересно, почему вы будете использовать loop/recur а не просто определять функцию, которая вызывает себя. Наша факториальная функция могла быть написана так:
(defn fact-no-loop [n]
(if (= 1 n)
1
(* n (fact-no-loop (dec n)))))
Это более красноречиво и работает аналогичным образом. Почему бы вы когда - либо использовать цикл и повторялись?
Оптимизация звонков
Если вы используете loop/recur , то компилятор (программное обеспечение, которое превращает Clojure-код в JTM-байт-код) знает, что вы хотите создать рекурсивный цикл. Это означает, что он пытается изо всех сил оптимизировать ваш код для рекурсии. Давайте сравним скорость fact и fact-no-loop :
(time (fact 20))
; => "Elapsed time: 0.083927 msecs"
; 2432902008176640000
(time (fact-no-loop 20))
; => "Elapsed time: 0.064937 msecs"
; 2432902008176640000
В этом масштабе разница незначительна. Фактически, fact-no-loop иногда быстрее, чем fact из-за непредсказуемого характера компьютерной памяти. Однако в более широком масштабе такая оптимизация может сделать ваш код намного, намного быстрее.
Реестр вложенности внутри функций
fact-no-loop работает без loop/recur потому что вся функция рекурсивна. Что, если мы хотим, чтобы часть нашей функции использовала рекурсивный цикл, а затем остальную часть, чтобы сделать что-то нерекурсивное? Нам нужно было бы определить две совершенно отдельные функции. Использование loop/recur позволяет использовать небольшую анонимную функцию.
|
Предыдущая |
Главная
| следующий
|
| Пусть привязки | Содержание | Чтобы добавить |
