97 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
		
		
			
		
	
	
			97 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
|   | --- | |||
|  | title: Clojure   Looprecur | |||
|  | localeTitle: Clojure Looprecur | |||
|  | --- | |||
|  | Возможно, вам придется понять, [`if`](//forum.freecodecamp.com/t/clojure-conditionals/18412) и [`let`](//forum.freecodecamp.com/t/clojure-create-local-variables-with-let/18415) полностью понять рекурсию в 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)))))  | |||
|  | ``` | |||
|  | 
 | |||
|  |  [IDEOne!](https://ideone.com/3iP3tI) | |||
|  | 
 | |||
|  | Вы заметите, что `(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  | |||
|  | ``` | |||
|  | 
 | |||
|  |  [IDEOne!](https://ideone.com/tpC0Xo) | |||
|  | 
 | |||
|  | В этом масштабе разница незначительна. Фактически, `fact-no-loop` иногда быстрее, чем `fact` из-за непредсказуемого характера компьютерной памяти. Однако в более широком масштабе такая оптимизация может сделать ваш код намного, намного быстрее. | |||
|  | 
 | |||
|  | ### Реестр вложенности внутри функций
 | |||
|  | 
 | |||
|  | `fact-no-loop` работает без `loop/recur` потому что вся функция рекурсивна. Что, если мы хотим, чтобы часть нашей функции использовала рекурсивный цикл, а затем остальную часть, чтобы сделать что-то нерекурсивное? Нам нужно было бы определить две совершенно отдельные функции. Использование `loop/recur` позволяет использовать небольшую анонимную функцию. | |||
|  | 
 | |||
|  | | [ Предыдущая](//forum.freecodecamp.com/t/clojure-create-local-variables-with-let/18415) | [ Главная ](//forum.freecodecamp.com/t/clojure-resources/18422) | следующий  |   | |||
|  | | [Пусть привязки](//forum.freecodecamp.com/t/clojure-create-local-variables-with-let/18415) | [Содержание](//forum.freecodecamp.com/t/clojure-resources/18422) | Чтобы добавить | |