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

97 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

---
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循环或while循环。如果你仔细想想这是有道理的。 `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` ,并且如果该条件不再为真,它将会中断。这意味着它们必须具有某种副作用(例如向`i`添加1)以使条件最终为假;否则,循环会永远持续下去。
## 递归
值得庆幸的是,Clojure确实有一个循环。这些循环使用递归 - 一个调用自身的函数。最简单的递归算法是找到正数阶乘(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:](//forum.freecodecamp.com/images/emoji/emoji_one/rocket.png?v=2 ":火箭:") [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代码转换为JVM字节码的软件)知道您要创建递归循环。这意味着它最努力地优化代码以进行递归。让我们比较`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:](//forum.freecodecamp.com/images/emoji/emoji_one/rocket.png?v=2 ":火箭:") [IDEOne吧](https://ideone.com/tpC0Xo)
在这种规模下,差异可以忽略不计。实际上,由于计算机内存的不可预测性, `fact-no-loop`偶尔比`fact`更快。但是,在更大范围内,这种优化可以使您的代码更快,更快。
### 在函数中嵌套递归
`fact-no-loop`在没有`loop/recur`情况下工作,因为整个函数是递归的。如果我们希望我们的函数的一部分使用递归循环,然后其余部分做一些非递归的东西怎么办?我们必须定义两个完全独立的函数。使用`loop/recur`让我们使用一些匿名函数代替。
| [![:point_left:](//forum.freecodecamp.com/images/emoji/emoji_one/point_left.png?v=2 "point_left")上一页](//forum.freecodecamp.com/t/clojure-create-local-variables-with-let/18415) | [![:book:](//forum.freecodecamp.com/images/emoji/emoji_one/book.png?v=2 ":书:")家![:book:](//forum.freecodecamp.com/images/emoji/emoji_one/book.png?v=2 ":书:")](//forum.freecodecamp.com/t/clojure-resources/18422) |下一个![:point_right:](//forum.freecodecamp.com/images/emoji/emoji_one/point_right.png?v=2 "point_right") |
| [让绑定](//forum.freecodecamp.com/t/clojure-create-local-variables-with-let/18415) | [目录](//forum.freecodecamp.com/t/clojure-resources/18422) |待补充|