freeCodeCamp/guide/russian/clojure/looprecur/index.md

7.4 KiB
Raw Blame History

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))))) 

:rocket: IDEOne!

Вы заметите, что (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 

:rocket: IDEOne!

В этом масштабе разница незначительна. Фактически, fact-no-loop иногда быстрее, чем fact из-за непредсказуемого характера компьютерной памяти. Однако в более широком масштабе такая оптимизация может сделать ваш код намного, намного быстрее.

Реестр вложенности внутри функций

fact-no-loop работает без loop/recur потому что вся функция рекурсивна. Что, если мы хотим, чтобы часть нашей функции использовала рекурсивный цикл, а затем остальную часть, чтобы сделать что-то нерекурсивное? Нам нужно было бы определить две совершенно отдельные функции. Использование loop/recur позволяет использовать небольшую анонимную функцию.

| :point_left: Предыдущая | :book: Главная :book: | следующий :point_right: |
| Пусть привязки | Содержание | Чтобы добавить |