Clojure - Loop/Recur

You may need to understand if and let to fully grasp recursion in Clojure.

for and while

Clojure does not have for loops or while loops. This makes sense, if you think about it. A for loop changes a variable, and that's not allowed in Clojure.

for (var i = 0; i < 10; i++) {
  console.log(i);
}

i++ means that we add one to the variable i every time the loop finishes -- a clear example of a variable being mutated.

while loops are less obviously reliant on changing variables, but they are, just as much as for loops are.

var i = 0;
while (i < 10) {
  console.log(i);
  i++;
}

while loops always have a condition, like i < 10, and will break if that condition is no longer true. This means that they have to have some kind of side effect (like adding 1 to i) so that the condition will eventually be false; otherwise, the loop would last forever.

Recursion

Thankfully, Clojure does have one loops of some kind. These loops use recursion -- a function that calls itself. The simplest recursive algorithm is one to find a positive number factorial (5 factorial, for example, equals 5 * 4 * 3 * 2).

(defn fact [x]
  (loop [n x 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 it!2

You'll notice that (loop [n x prod 1] ...) looks quite similar to a let binding. It actually works in just the same way -- here, we bind n to x, and prod to 1.

Every recursive function has a "base case". This is the condition that makes the loop stop looping. In this case, our loop stops if n = 1, and returns prod. If n isn't equal to 1, then the loop recurs.

(recur (dec n) (* prod n))

This recur function restarts the loop, but with different bindings. This time, n isn't bound to x, but is instead bound to (dec n) (which means decrement n, or n - 1), and prod is bound to (* prod n).

So when we call the function, this is what happens:

(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

The ingenious thing about recursion is that the variables themselves are never changed. The only thing that changes is what n and prod refer to. We never say, n--, or n += 2.

Why use loop/recur?

You might be wondering why you would use loop/recur rather than simply defining a function that calls itself. Our factorial function could have been written like this:

(defn fact-no-loop [n]
  (if (= 1 n)
    1
    (* n (fact-no-loop (dec n)))))

This is more concise, and works in a similar way. Why would you ever use loop and recur?

Tail Call Optimisation

If you use loop/recur, then the compiler (the software that turns Clojure code into JVM bytecode) knows that you want to create a recursive loop. This means that it tries its hardest to optimise your code for recursion. Let's compare the speed of fact and 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 it!

At this scale, the difference is negligible. In fact, fact-no-loop is occasionally faster than fact due to the unpredictable nature of computer memory. However, on a larger scale, this kind of optimisation can make your code much, much quicker.

Nesting Recursion Within functions

fact-no-loop works without loop/recur because the entire function is recursive. What if we wanted part of our function to use a recursive loop, and then the rest of it to do something non-recursive? We'd have to define two entirely separate functions. Using loop/recur lets us use a little anonymous function instead.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值