Question

I've been developing in Java and Perl for a long time but wanted to learn something new so I've begun looking into clojure. One of the first things I've tried was a solution for the Towers of Hanoi puzzle but I've been getting strange behavior on my pretty-print function. Basically, my for loop is never entered when I run it with 'lein run' but it seems to work fine when I run it from the repl. Here's a stripped down example:

(ns test-app.core
  (:gen-class))

(defn for-print
  "Print the same thing 3 times"
  [ p-string ]
  (println (str "Checkpoint: " p-string))
  (for
    [x [1 2 3]]
    (printf "FOR: %s\n" p-string)
))


(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (for-print "Haldo wurld!"))

When I run this with 'lein run' I only see the output from the "Checkpoint" println. If I remove that line I get no output at all. But, if I run 'lein repl' and then type (-main) it prints the string 3 times, as expected:

test-app.core=> (-main)
Checkpoint: Haldo wurld!
(FOR: Haldo wurld!
FOR: Haldo wurld!
FOR: Haldo wurld!
nil nil nil)
test-app.core=> 

What's going on here? I have a feeling that I'm approaching this the wrong way, trying to use my past Perl/Java mentality to write clojure. What would be an idiomatic way to run the same task a set number of times?

Was it helpful?

Solution

The for loop is returning a lazy sequence that is evaluated only as needed.

When you run the program inside the repl the for result is realized in order to display the result on screen.

But when you run using lein run result is never used so the collection is not realized.

You have a couple alternatives:

1) use doall outside the for loop for force lazy sequence realization

Ex:

(defn for-print
 "Print the same thing 3 times"
 [ p-string ]
 (println (str "Checkpoint: " p-string))
 (doall (for
    [x [1 2 3]]
    (printf "FOR: %s\n" p-string))))

2) since you're only printing which is a side effect and not really creating a collection you can use doseq.

Ex:

  (defn for-print
  "Print the same thing 3 times"
  [ p-string ]
  (println (str "Checkpoint: " p-string))
  (doseq [x [1 2 3]]
   (printf "FOR: %s\n" p-string)))

OTHER TIPS

Clojure for is not an imperative loop (you should avoid thinking about loops in Clojure at all), it's a list comprehension, which returns lazy sequence. It's made for creating a sequence, not for printing anything. You can realize it, and make it work, but it is bad way to go.

As Guillermo said, you're looking for doseq macro, which is designed for side effects. It's probably the most idiomatic Clojure in your particular case.

From my point of view the most similar to imperative loops construction in Clojure is tail recursion, made with loop/recur. Still it's rather low level construct in Clojure and certainly should not be used in a imperative loop manner. Have a better look into functional programming principles, as well as Clojure core functions. Trying to transfer Java/Perl thinking to Clojure may harm you.

The other answers are correct, and provide details. I want to add some higher-level clarification that might be helpful. In most languages, "for" means "for such and conditions, perform such and such actions (which could be of an arbitrary type, including side-effects)." This kind of thing can be done in Clojure, and I have seen experienced Clojure programmers do it when it's useful and convenient. However, using loops with side-effects works usually against the strengths of the language. So in Clojure the word "for" has a different meaning in most languages: Generating (lazy) sequences. It means something like "For these inputs, when/while/etc. they meet such and such conditions, bind then temporarily to these variables, generate a (lazy) sequence by processing each set of values in such and such a way."

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top