recur
While for
is somewhat like a loop,
recur
is a real loop in Clojure.
recur
represents such a remarkable idea that we might even say, “this is Clojure.”
If you have a programming background, you may have heard of tail recursion,
which is a major feature of functional languages.
This recur
special form is the one that implements tail recursion.
As the words “tail recursion” indicate, recur
must be called in the tail position.
In other words, recur
must be the last thing to be evaluated.
The syntax of the recur
macro is: (recur exprs*)
Look at these simple examples:
user> (defn some-join [coll result]
(if (= 1 (count coll)) (str result (first coll))
(recur (rest coll) (str result (first coll) ", "))))
#'user/some-join
user> (some-join ["hello" "world" "love" "coding"] "Words: ")
"Words: hello, world, love, coding"
user> ; when we want to do something just before the recur
user> ; we can use *do*
user> (defn some-join [coll result]
(if (= 1 (count coll)) (str result (first coll))
(do
(println result)
(recur (rest coll) (str result (first coll) ", ")))))
#'user/some-join
user> (some-join ["hello" "world" "love" "coding"] "Words: ")
Words:
Words: hello,
Words: hello, world,
"Words: hello, world, love, coding"
user> ; however, just for printing out the process, let binding works
user> (defn some-join [coll result]
(let [_ (println result)]
(if (= 1 (count coll)) (str result (first coll))
(recur (rest coll) (str result (first coll) ", ")))))
#'user/some-join
user> (some-join ["hello" "world" "love" "coding"] "Words: ")
Words:
Words: hello,
Words: hello, world,
Words: hello, world, love,
"Words: hello, world, love, coding"
user> ; we attempted the same thing as clojure.string/join function does
user> (str "Words: " (clojure.string/join ", " ["hello" "world" "love" "coding"]))
"Words: hello, world, love, coding"
Even though we can write code without recur
, the use of recur
is strongly recommended in Clojure
because of tail-call optimization (TCO).
Compare a recursive sum-up function and a sum-up-with-recur function that uses recur
.
The recursive call raises StackOverflowError when summing up to 10000.
On the other hand, recur
can sum up even 100000.
This is why tail-call optimization (TCO) works effectively.
user> (defn sum-up [coll result]
(if (empty? coll) result
(sum-up (rest coll) (+ result (first coll)))))
#'user/sum-up
user> (sum-up (range 10) 0)
45
user> (sum-up (range 10000) 0)
StackOverflowError clojure.lang.ChunkedCons.more (ChunkedCons.java:47)
user> (defn sum-up-with-recur [coll result]
(if (empty? coll) result
(recur (rest coll) (+ result (first coll)))))
#'user/sum-up-with-recur
user> (sum-up-with-recur (range 10) 0)
45
user> (sum-up-with-recur (range 10000) 0)
49995000
user> (sum-up-with-recur (range 100000) 0)
4999950000
In every computer language, we can write recursive calls. However, recursion with TCO is a Clojure-specific feature. With TCO, Clojure is good at processing a huge list.
ClojureDocs
Clojure.org, Special Forms, recur
http://clojure.org/special_forms#Special%20Forms–(recur%20exprs*)
Clojure from the ground up: macros, Recursion
http://aphyr.com/posts/305-clojure-from-the-ground-up-macros
Clojure for the Brave and True, Functional Programming, 2.1 Recursion instead of for/while
http://www.braveclojure.com/functional-programming/#2_1__Recursion_instead_of_for_while
GetClojure