1

I am really not sure what is the problem here. I started to experience this "issue" with this kind of code:

First I did define that string with some metadata:

(def ^{:meta-attr ["foo" "bar"]
   :meta-attr2 "some value"} foo "some value")

Then I did create the following two functions:

(defn second-fn [values]
  (for [x values] (println x)))

(defn first-fn [value]
  (doseq [[meta-key meta-val] (seq (meta value))]
    (if (= meta-key :meta-attr)
      (second-fn meta-val))))

Now when I run this command in the REPL:

(first-fn #'foo)

I am getting nil.

However, if I change second-fn for:

(defn second-fn [values]
  (println values))

And if I run that command again, I am getting this in the REPL:

user> (first-fn #'foo)
[foo bar]
nil

What I was expecting to get in the REPL with the first version of my function is the following:

user> (first-fn #'foo)
foo
bar
nil

But somehow, I think there is something I don't get that is related to the bound variable by doseq.

Here is another set of functions that has exactly the same behavior:

(defn test-2 [values]
;  (println values))
  (for [x values] (println x)))

(defn test-1 [values]
  (doseq [x values]
    (test-2 x)))

(test-1 [["1.1" "1.2"] ["2"] ["3"]])

I think I am missing some Clojure knowledge to understand what is going on here. Why it looks like good when I println or pprint the value in the second function, but the for is not working...

Update and Final Thoughts

As answered for this question, the problem has to do with lazyness of the for function. Let's take the simplest example to illustrate what is going on.

(defn test-2 [values]
  (for [x values] (println x)))

(defn test-1 [values]
  (doseq [x values]
    (test-2 x)))

What happens there is that in test-1, every time that doseq "iterate", then a new non-lazy sequence is being created. That means that they are accessible like any other collection during the "looping".

doseq should generally be used when you work with non-pure functions that may have side effects, or I think when you are playing with relatively small collections.

Then when test-2 is called, the for will create a lazy-seq. That means that the sequence exists, but that it never did get realized (so, each step hasn't been computed yet). As is, nothing will happen with these two functions, since none of the values returned by the for have been realized.

If we want to keep this doseq and this for loops, then we have to make sure that for get realized in test-2. We can do this that way:

(defn test-2 [values]
  (doall (for [x values] (println x))))

(defn test-1 [values]
  (doseq [x values]
    (test-2 x)))

That doall does here, is to force the full realization of the sequence returned by the for loop. That way, we will end with the expected result.

Additionally, we could realize the lazy-seq returned by for using other functions like:

(defn test-2 [values]
  (first (for [x values] (println x))))

(defn test-2 [values]
  (count (for [x values] (println x))))

None of this make sense, but all of these examples for the realization of the lazy-seq returned by the for.

Additionally, we could have simply used two doseq like this:

(defn test-2 [values]
  (doseq [x values] (println x)))

(defn test-1 [values]
  (doseq [x values]
    (test-2 x)))

That way, we don't use any lazy-seq and so we don't have to realize anything since nothing is evaluated lazilly.

Neoasimov
  • 1,111
  • 2
  • 10
  • 17

1 Answers1

2
  • for is lazy, while doseq is eager.
  • for is "functional" (values) and doseq is "imperative" (side-effects).

In other words, you should not be using for in second-fn, since you seem to be worried only with side-effects. What you are actually doing there is building a lazy sequence (which, it seems, is never executed).

See Difference between doseq and for in Clojure for further info.

Community
  • 1
  • 1
rsenna
  • 11,775
  • 1
  • 54
  • 60
  • Wonderful! I knew I was missing a core concept here :) I will continue to test according to what you said and ask further clarifications before updating my question accordingly. However, you said "since you seem to be worried only with side-effects". Maybe I am not understanding what you are saying, but in fact it is quite the opposite: I am *not* worried by side-effects since these are pure functions, no? – Neoasimov May 21 '14 at 20:38
  • one question regarding this. It appears that I can "fix" this issue by using only `doseq` or only `for` within the two functions. What I am not clear about is what I should be doing, and what are the consequences of each of the methods? – Neoasimov May 21 '14 at 20:48
  • I thought you wanted side-effects, because you used `println` (an impure function). Because of that, both `second-fn` and `first-fn` are impure, since impurity is "contagious", so to speak. Also, sorry if my answer was not clear enough, English is not my first language. :P – rsenna May 21 '14 at 20:54
  • no worries, it is not mine neither ;) Yeah, this is what I discovered: that println is impure; but really, I was just using it to see why it was not working as I thought it would :) So no, these functions are mean to be pure. Considering that, I should be using lazy seqs (`for`) instead of `doseq`. But I am still not 100% sure what are the consequences of using one over the other. Any ideas? – Neoasimov May 21 '14 at 21:03