Simple iteration in Clojure

Imperative programmers frequently complain that one of the things that makes functional languages hard to learn is their lack of loops. Clojure actually does provide quite a few methods for doing iteration. We’ll have a look at some of them here.

Suppose we want to print out a table of squares of certain numbers. In Clojure, we can provide these numbers in a list, as in ‘(1 3 5 8 19) (remember the quote at the start is needed to stop Clojure interpreting the list as a function). To an imperative programmer, the way to do this is to set up a loop over each element in the list, calculating the square of each number. How can we do the equivalent in Clojure?

We can use the doseq macro. The code is

(defn squares-table [numlist]
  (doseq [num numlist]
    (println (str "Square of " num " = " (* num num)))))

The function squares-table takes a list as an argument. The doseq macro takes two arguments. The first, num here, is undefined before doseq is run. It is assigned each element of numlist in turn, and for each element, the form or forms (you can put any number of forms after the argument list) are executed. In this case, we just print out a string giving the square of num.

We could call this function in the REPL with

(squares-table ‘(1 3 5 8 19))

and we get the result:

Square of 1 = 1
Square of 3 = 9
Square of 5 = 25
Square of 8 = 64
Square of 19 = 361
nil

The ‘nil’ at the end is just the return value of squares-table.

Another iteration macro is dotimes. A variant of our squares-calculating program looks like this:

(defn squares-times [maxnum]
  (dotimes [num maxnum]
    (println (str "Square of " num " = " (* num num)))))

This time, squares-times takes a single argument, maxnum, not a list. The dotimes macro takes two arguments. The first, num here, is undefined at the start. It takes on each integer value from 0 up to maxnum – 1. For each value of num, the body of the dotimes is run, so in this case, we’ll get a table of squares from 0 up to maxnum – 1. Incidentally, maxnum can also be a character, in which case it is interpreted as that character’s ASCII code. So if we made the call (square-times \A), we’d get a table of squares from 0 up to 64, since ‘A’ has ASCII code 65. The backslash before the A is needed, since otherwise, Clojure will interpret A as a symbol, not a bare character.

Although the doseq and dotimes macros look quite powerful, they have one limitation which results in them not being used as much as you might think. The problem is that they both return only ‘nil’, so they don’t produce any data that can be used in subsequent functions. You might think that this is no big deal, since a ‘for’ loop in an imperative language doesn’t return anything either, but you can, of course define other data structures such as arrays that are constructed within a ‘for’ loop and are then used in subsequent code.

Clojure provides several other macros and functions that do return useful data, usually in the form of a list, and these macros are used much more than doseq and dotimes. One powerful function is map.

In its simplest form, map takes two arguments. The first is a unary function (that is, a function that takes a single argument), and the second is a list. map iterates through the list and applies the function to each item in the list in turn. The results are returned as a new list.

For example, suppose we wanted to create a list of all the squares of numbers within a given range. The following code does this.

(defn square [num]
  (* num num))

(defn squares-map [minnum maxnum]
  (map square (range minnum (inc maxnum))))

First, we define a simple unary function square which squares its argument. Then we define squares-map which takes two arguments. Then we call map to create the list of squares. The first argument to map is our square function that we’ve just defined. The second argument must be a list. We’ve used the range function (part of Clojure). When given two integer arguments, range returns a list starting with the first argument and ending with one less than the last argument. Since we want the final value maxnum to be included in our answer, we apply the inc function to add 1 to maxnum before sending it to range.

map will thus apply square to each number from minnum to maxnum and return the result as a list. For example, if we call (squares-map 3 9) we get back (9 16 25 36 49 64 81). Clearly the Clojure code for doing this is a lot shorter than in, say, Java. (OK, so a lot of work is being done for you by the people who wrote map and range and so on, but these functions are part of the Clojure language, rather than being optional add-ons, so you can rely on any installation of Clojure having them.)

Note, by the way, that this example shows a function square being passed as a parameter to another function map. This shows that functions are just data that can be passed around like any other kind of data; this is something that many imperative languages don’t let you do.

map is actually a lot more powerful than this simple example showed. In its more general form, it takes a function that takes any number of arguments, followed by the same number of lists of data. It will then pick off the first element from each list and pass these as arguments to the function, then it will pick out the second element from each list and pass those as arguments, and so on until it comes to the end of the shortest list, at which point it will stop and ignore all remaining data in other lists.

For example, suppose we had two lists of numbers and wanted to calculate a number that is the square of an element in the first list added to the square of the corresponding number in the second list. We can do this as follows.

(defn sum-squares [x y]
  (+ (square x) (square y)))

(defn add-squares [nums1 nums2]
  (map sum-squares nums1 nums2))

We first define a function sum-squares that takes two arguments and returns the sum of their squares (using the square function from earlier). Then we define add-squares that takes two lists, num1 and num2. We call map, giving it the sum-squares function and the two lists. For example, if we called (add-squares ‘(1 3 5 7) ‘(2 4 6)) we get back (5 25 61). This is because 1*1 + 2*2 = 5, 3*3 + 4*4 = 25 and 5*5 + 6*6 = 61. The final 7 in the first list is ignored since there is nothing in the second list to match it with.

Finally, we’ll have a look at the filter function. filter iterates through a list and tests each list element against a predicate function (a function that returns true or false). If an element is ‘true’ according to the predicate, it is included in a new list, otherwise it is excluded. The return from filter is a list of all elements from the original list that pass the predicate test.

For example, suppose we wanted a list of all the odd squares within a given range. We could do that using filter as follows.

(defn is-odd? [num]
  (= (rem num 2) 1))

(defn odd-squares [minnum maxnum]
  (let [numlist (range minnum (inc maxnum))
        squares (map square numlist)]
    (filter is-odd? squares)))

Our predicate function is-odd? tests if a single number is odd by calculating its remainder when divided by 2. The odd-squares function uses the map from our earlier function to calculate the list of squares within a given range, and then filter is applied to this list. We’ve used a let to make the code more readable. We could have written the whole thing on one line like this:

(defn is-odd? [num]
  (= (rem num 2) 1))

(defn odd-squares [minnum maxnum]
  (filter is-odd? (map square (range minnum (inc maxnum)))))

However, deciphering this code requires a fair bit of backwards thinking, so it’s not very human-readable. (Actually, we could have included the definition of is-odd? within that single line as well, as an anonymous function, but more on that when we consider ways of creating functions.)

Testing this function, we call (odd-squares 3 16) and get back (9 25 49 81 121 169 225).

Hopefully these little examples have illustrated the power and brevity of a lot of Clojure code.

Advertisements
Post a comment or leave a trackback: Trackback URL.

Comments

  • Octopusgrabbus  On February 5, 2012 at 3:46 PM

    I have not seen as clear a reason why doseq and other do forms are not always useful, especially when you need to return something other than nil. Thanks.

Trackbacks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: