1

In my continual effort to replicate imperative programming in Clojure, I seem to still nest for functions.

For example, if I wanted to start with:

[[{:a 1 :b 2 :c 3} {:a 1 :b 2 :c 3}] [{:a 2 :b 2 :c 3} {:a 2 :b 2 :c 3}]]

and get:

[[:table [:tr [:td {:class "a-1" "b-2" "c-3"}] [:td {:class "a-2" "b-2" "c-3"}]]

how would I do that without the classic nested for statements I'm familiar with from other languages?

I realize I should paste my attempt here but it's too awful.

UPDATE: see the point below that I used incorrect hiccup in the :class values.

1 Answer 1

3

My recommendation would be to see the structure of the data (which you have already) and try to decompose the problem in terms of the data you are given.

You have a structure which resembles rows and cells, so that's one approach. You format rows, which are the result of formatting individual cells, then each cell needs to have CSS classes which are passed as a hash-map.

With that in mind:

(defn cell-classes [cell]
  (->> (map (fn [[k v]] (str (name k) "-" v)) cell)
       (interpose " ")
       (reduce str)))

(defn format-cell [cell]
  (let [classes (cell-classes cell)]
    [:td {:class classes}]))

(defn format-row [cells]
  (->> (map format-cell cells)
       (into [:tr])))

(defn format-rows [rows]
  (->> (map format-row rows)
       (into [:table])))

If we test it with your sample data:

(clojure.pprint/pprint (format-rows [[{:a 1 :b 2 :c 3} {:a 1 :b 2 :c 3}] [{:a 2 :b 2 :c 3} {:a 2 :b 2 :c 3}]]))

it prints

[:table
 [:tr [:td {:class "a-1 b-2 c-3"}] [:td {:class "a-1 b-2 c-3"}]]
 [:tr [:td {:class "a-2 b-2 c-3"}] [:td {:class "a-2 b-2 c-3"}]]]

PS: there's a minor issue in your desired output, which is that {:class "a-1" "b-2" "c-3"} would not be a desirable output for hiccup, that's why I joined the class names with space on cell-classes.

Sign up to request clarification or add additional context in comments.

5 Comments

Whoa! That is very helpful. I really like the idea of using functions like this, the result of decomposition, as it is much more readable than the (somewhat non-working) nested spaghetti code I was getting into. I am realizing that using functions with names like this are way more handy mentally. They are probably a lot more reusable for other projects, too.
I should mention that although my title mentions for functions, I was also getting lost in loop and recur attempts.
I also really like how your example would work with all kinds of key value pairs in the maps without having to specify them ahead of time in the functions.
When you tackled this, what was your sequence of thoughts? Did you more or less figure out the functions in the order they are listed? That is, tackling the problem from the inside out?
In this example I started from format-rows and wrote the functions I needed on top of each other (format-rows, then format-row, then format-cell with an inline function, then I extracted it to cell-classes). There are cases where bottom-up feels more natural, but I guess you develop some intuition and go one way or the other depending on your ideas. I tend to use the arrow macros a lot but some people prefer to use let blocks and meaningful names when possible on every stage of the "transformation pipeline" and that's OK too.

Your Answer

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.