tags:

views:

66

answers:

2

Hi, I've a code like this. I can run this in repl but can't from command line. I guess i've a lazy evaluation problem.


; items.clj

(def items (ref []))

(defn init-items []
  (map
    #(dosync
       (alter items conj %))
    ["foo" "bar" "baz" ] ))

(init-items)
(println (first @items))

$ java -jar clojure.jar items.clj
$ nil

Regards.

+4  A: 

Got it!

solution

Clojure is not motivated to run the map function in init-items because there's no result returned. I wrapped that into a doall to force execution, and presto.

Carl Smotricz
I worked. Thanks alot.
Osman
Actually `dorun` is better suited to this case (`doall` holds onto the head of the seq it wraps and returns it, whereas `dorun` discards it step by step and finally returns `nil` -- thus it's better suited to side-effecty code).
Michał Marczyk
@Michal: You're absolutely correct, of course. I've dabbled a bit with Clojure but never reached high proficiency, and now, sadly, even that's starting to rust. Thanks for the correction!
Carl Smotricz
Actually doseq is better suited for side-effects. dorun is so ugly.
kotarak
@kotarak: Good point. I was under the impression `doseq` was lazy but looking at the API it seems I was mistaken.
Carl Smotricz
@Carl: there is the magic hint: *do*seq. ;) "do" in front normally means non-laziness.
kotarak
@kotarak: Thanks! :)
Carl Smotricz
+3  A: 

Some alternatives:

If you just want to add a bunch of items to a collection held in a Ref, starting one transaction per item and conjing them separately is a bit wasteful. Instead, you could do

(defn init-items []
  (dosync (alter items into ["foo" "bar" "baz"])))

If you do have some reason to do it in a one-item-per-step fashion, I think the most idiomatic and convenient approach would currently be to use doseq:

(defn init-items []
  (doseq [item ["foo" "bar" "baz"]]
    (dosync (alter items conj item))))

(Or you could move wrap the entire doseq in a dosync and not use dosync in the doseq's body.)

Michał Marczyk