6
\$\begingroup\$

I wrote the following code which transforms a nested data structure

(def board [[{:mine true} {:warn 1 :explored true} {:explored true}]
 [{:warn 1 :explored true} {:warn 1 :explored true} {} ]
 [{} {} {:flag true } ]])

into a printable form and prints it

(("[ ]" "[1]" "[E]") ("[1]" "[1]" "[ ]") ("[ ]" "[ ]" "[F]"))

The functions for the transformation are the following:

(defn cell->icon 
 [cell]
 (letfn [(cell->str [v] (format "[%s]" v))] 
 (if (:explored cell) 
 (cond (:mine cell) (cell->str "M")
 (:warn cell) (cell->str (:warn cell))
 :else (cell->str "E"))
 (cond (:flag cell) (cell->str "F")
 :else (cell->str " ")))))
(defn board->icons
 [board]
 (map (partial map cell->icon) board))

So far so good (if you have any recommendations for a nicer approach though, do not hesitate to mention).

The function which I don't like though is the following:

(defn print-board
 [board]
 (doall 
 (map println
 (map (partial clojure.string/join " ")
 (board->icons board)))))

I don't like to use println together with map since it is not a pure function (has side-effects)!? Maybe I am a bit too critical but I would be glad if somebody could advice me or give a hint how to do it in a nicer Clojure like way.

ferada
11.4k25 silver badges65 bronze badges
asked Jan 10, 2016 at 19:19
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Separate impure IO from data transformations

I wrote the following code which transforms a nested data structure ... into a printable form

Not only does your code transform the data into a printable form, it also prints it.

To better isolate the side effects of printing, you should separate these two operations: first assemble a formatted string, then you can do whatever you want with it -- print it with a single call to println, save it, send it over the network, etc.

(defn format-board
 [board]
 (->> (board->icons board)
 (map (partial str/join " "))
 (str/join "\n")))
(def print-board
 (comp println format-board))

doseq

As a side note, instead of (doall (map println seq-to-print)) it is common to see

(doseq [line seq-to-print]
 (println line))

which is usually what you want, since

  • doseq does not hold onto the return value of println, whereas doall builds up an entire return sequence in memory (important when the sequence is big!) -- which is why your original print-board returns (nil nil nil) instead of nil; and
  • this makes it more visually clear that you are doing something with each item in the sequence, instead of generating a new sequence with map
ferada
11.4k25 silver badges65 bronze badges
answered Jan 11, 2016 at 6:08
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.