HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Printing a nested data structure in Clojure

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
clojurenestedprintingstructuredata

Problem

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.

Solution

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

Context

StackExchange Code Review Q#116378, answer score: 2

Revisions (0)

No revisions yet.