trzczy
trzczy

Reputation: 1491

Printing inside a for loop

I was practicing one Clojure tutorial and had to ensure that a for loop was executed so I put a println command there, but it did not display messages.

So now I have got the question...

This code prints Tom's name:

(ns tutorial.core)
(defn -main []
  (println 'Jane)
  (for [a ['Tom]]
    (println a))
;;    'Kate
)



tutorial.core> (-main)
Jane
Tom
(nil)
tutorial.core> 

but this not:

(ns tutorial.core)
(defn -main []
  (println 'Jane)
  (for [a ['Tom]]
    (println a))
    'Kate
)


tutorial.core> (-main)
Jane
Kate
tutorial.core>

Why? In which cases can we expect that println will not print texts?

Upvotes: 0

Views: 285

Answers (2)

Alan Thompson
Alan Thompson

Reputation: 29958

As Lee says, if you only want side effects like printing, a doseq is the best solution as it never returns a value other than nil.

If you do want to use a for loop, you can remove the laziness by wrapping it inside a (vec ...) expression, which will force the for loop to run immediately. Thus we get:

(println :start)
(vec
  (for [a [1 2 3]]
    (println a)))
(println :end)

with result:

:start
1
2
3
:end

Without the vec, we get the behavior you saw:

(println :start)
(for [a [1 2 3]]
  (println a))
(println :end)

with result:

:start
:end

I almost never want a lazy result, as the uncertainty over when a computation occurs can make debugging difficult. I use the above construct so often that I wrote a small macro forv that always returns a vector result, similar to the mapv function.

Upvotes: 1

Lee
Lee

Reputation: 144136

for is not a loop, it is a sequence comprehension which returns a lazy sequence. Your for expression will therefore only execute its side-effects (calls to println) when the returned sequence is evaluated. The REPL evaluates the values returned from your calls to -main so it can print them.

Your first example returns a lazy sequence which is evaluted by the REPL causing the (println 'Tom) call to be evaluated. Since println returns nil, the resulting sequence contains a single nil value - this is the (nil) you see in the output.

Your second example creates the same sequence but does not evaluate it, instead 'Kate is returned from the function and the REPL prints that.

If you want an imperative for loop you should use doseq:

(defn -main []
  (println 'Jane)
  (doseq [a ['Tom]]
    (println a))
  'Kate)

Upvotes: 1

Related Questions