Reputation: 1373
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:
Thanks, R.
Upvotes: 0
Views: 560
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 nil
s 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
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
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