2
votes

I’m trying to learn to program with the book Clojure for the Brave and True (CFTBAT). At the end of the crash course, the author makes us write a small programm to illustrate looping in Clojure. To explain the looping and recursing part of the program, here, the author writes a smaller example using loop and then shows it’s possible to replace loop with a normal function definition.

It’s this normal function definition example I can’t understand. Here is the code:

(defn recursive-printer
    ([]
        (recursive-printer 0))
    ([iteration]
        (println iteration)
        (if (> iteration 3)
            (println "Bye!")
            (recursive-printer (inc iteration)))))

(recursive-printer)

I don’t understand the code because I can’t see where are the arguments of the function recursive-printer. In Clojure, the arguments of a function are supposed to be in brackets and the body in parenthesis. So, in this example, the arguments would be an empty argument [] and iteration. But then why are they put between parenthesis too?

And what is (recursive-printer 0) Is it a function call, where the function calls itself?

If someone could explain me how this piece of code works, that would be much appreciated.

2
There are two different argument lists. One [] -- with code to handle the case where it's called with no arguments -- and one [iteration], to handle when it's called with one argument.Charles Duffy
BTW, inasmuch as this book is available online, it might not hurt to have a link -- the web version isn't oriented page-by-page, so "page 63" doesn't help people who are using it very much.Charles Duffy
See Chapter 3 on "Arity Overloading": braveclojure.com/do-thingsCharles Duffy
@CharlesDuffy I edited the question to add the links. Thanks, I missed the arity overloading! So, if the question is called without an argument, the function calls itself with an argument (0) and inc this argument,iterationuntil it reaches 4?guillaume8375
That's exactly right.Charles Duffy

2 Answers

2
votes

In clojure you can define a function such that it can take different numbers of arguments e.g.

(defn foo []
  ....)

is a function which takes no arguments. It is called like this ..

(foo)

(defn foo [x]
  ...)

is a function which takes 1 argument. It can be called like

(foo :a)

but sometimes, you may want to define a function which takes zero or 1 argument. To do this, you use a slightly different format

(defn foo
  ([] ;no argument form 
    ...)
  ([x] ;single argument form
    ...))

A common idiom is to use a zero argument form in a recursive function definition and include a single 'accumulator' argument form as the main part of the function. So, looking at your example, you have

(defn recursive-printer
    ([] ; zero argument form
        (recursive-printer 0))
    ([iteration] ; 1 argument form
        (println iteration)
        (if (> iteration 3)
            (println "Bye!")
            (recursive-printer (inc iteration)))))

(recursive-printer)

When you call (recursive-printer) it calls the first form (zero argument form). That form in turn calls the function with a single argument of 0. This calls the second form.

The second form first prints out the argument, then tests to see if it is greater than 3, which in the first call it is not as it is 0, so it executes the 'else' statement, which does a recursive call with a new argument which is the current argument increased by 1. Now your argument is 1 and it is printed out. the test is still false as 1 is not > 3, so the function is called again with the argument increased by 1 i.e. 2. In this call, 2 is printed, the test is still not grater than three, so the function is called again with the argument increased to 3. In this call, 3 is printed and the test is still not > 3, so the argument is incremented to 4 and the function called again with 4 as the argument. The value 4 is printed, but this time 4 is > 3, so the string "Bye" is printed and as this is the terminating condition, no further recursive call is made and the stack unwinds and the function terminates.

1
votes

We can drop the zero arity:

(defn recursive-printer [iteration]
  (println iteration)
  (if (> iteration 3)
    (println "Bye!")
    (recursive-printer (inc iteration))))

... and call the function with an explicit 0 argument:

(recursive-printer 0)
0
1
2
3
4
Bye!
=> nil

This lets us concentrate on the recursion. Since the recursive call is the last thing done (in tail position), we can use recur instead:

(defn recursive-printer [iteration]
  (println iteration)
  (if (> iteration 3)
    (println "Bye!")
    (recur (inc iteration))))

... with exactly the same effect.

The extra arity just confuses things, I think.