ibercode
ibercode

Reputation: 1373

Clojure loop collection

I want to know if this is the right way to loop through an collection:

(def citrus-list ["lemon" "orange" "grapefruit"])

(defn display-citrus [citruses]
  (loop [[citrus & citruses] citruses]
    (println citrus)
    (if citrus (recur citruses))
    ))

(display-citrus citrus-list)

I have three questions:

  1. the final print displays nil, is it ok or how can avoid it?
  2. I understand what & is doing in this example but I don´t see it in other cases, maybe you could provide a few examples
  3. Any other example to get the same result?

Thanks, R.

Upvotes: 0

Views: 560

Answers (3)

Ivan Grishaev
Ivan Grishaev

Reputation: 1681

Answering your last question, you should avoid using loop in Clojure. This form is rather for experienced users that really know what they do. In your case, you may use such more user-friendly forms as doseq. For example:

(doseq [item collection]
  (println item))

You may also use map but keep in mind that it returns a new list (of nils if your case) that not sometimes desirable. Say, you are interested only in printing but not in the result.

In addition, map is lazy and won't be evaluated until it has been printed or evaluated with doall.

Upvotes: 2

leetwinski
leetwinski

Reputation: 17859

First of all your implementation is wrong. It would fail if your list contains nil:

user> (display-citrus [nil "asd" "fgh"])
;;=> nil
nil

And print unneeded nil if the list is empty:

user> (display-citrus [])
;;=> nil
nil

you can fix it this way:

(defn display-citrus [citruses]
  (when (seq citruses)
    (loop [[citrus & citruses] citruses]
      (println citrus)
      (if (seq citruses) (recur citruses)))))

1) it is totally ok: for non-empty collection the last call inside function is println, which returns nil, and for empty collection you don't call anything, meaning nil would be returned (clojure function always returns a value). To avoid nil in your case you should explicitly return some value (like this for example):

(defn display-citrus [citruses]
  (when (seq citruses)
    (loop [[citrus & citruses] citruses]
      (println citrus)
      (if (seq citruses) (recur citruses))))
  citruses)

user> (display-citrus citrus-list)
;;=> lemon
;;=> orange
;;=> grapefruit
["lemon" "orange" "grapefruit"]

2) some articles about destructuring should help you

3) yes, there are some ways to do this. The simplest would be:

(run! println citrus-list)

Upvotes: 2

user5239066
user5239066

Reputation:

For most purpose, you can use either map, for or loop.

=> (map count citrus-list)
(5 6 10)

=> (for [c citrus-list] (count c))
(5 6 10)

=> (loop [[c & citrus] citrus-list
           counts []]
     (if-not c counts
       (recur citrus (conj counts (count c)))))
[5 6 10]

I tend to use map as much of possible. The syntax is more concise, and it clearly separates the control flow (sequential loop) from the transformation logic (count the values).

For instance, you can run the same operation (count) in parallel by simply replacing map by pmap

=> (pmap count citrus-list)
[5 6 10]

In Clojure, most operations on collection are lazy. They will not take effect as long as your program doesn't need the new values. To apply the effect immediately, you can enclose your loop operation inside doall

=> (doall (map count citrus-list))
(5 6 10)

You can also use doseq if you don't care about return values. For instance, you can use doseq with println since the function will always return nil

=> (doseq [c citrus-list] (println c))
lemon
orange
grapefruit

Upvotes: 1

Related Questions