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.
1 Answer 1
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 ofprintln
, whereasdoall
builds up an entire return sequence in memory (important when the sequence is big!) -- which is why your originalprint-board
returns(nil nil nil)
instead ofnil
; 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