How to generate more readable Clojure code?

Some of my code is generated from Clojure data with fipp:

And I got:

(defn read-next [db op-data sid op-id op-time]
  (let [book-id op-data
        user-id (get-in db [:sessions sid :user-id])
        book-content (get books-cache book-id)]
    (update-in
     db
     [:users user-id :readings book-id]
     (fn [reading]
       (if (< (:progress reading) (dec (count book-content)))
         (-> reading
             (update :progress inc)
             (update
              :messages
              (fn [messages]
                (assoc
                 messages
                 op-id
                 (merge
                  schema/message
                  {:kind :content,
                   :text (get book-content (:progress reading)),
                   :time op-time,
                   :id op-id,
                   :book-index (:progress reading)})))))
         (do (println "Already finished book") reading))))))

It appears to me the code is not optimal since the content looks like being pushed from right to left. I know it’s because I added {:width 92} in my code. I could increase the number, but it won’t always solve the problem.

I chose fipp since it was quite fast and it allowed to generate code from lists and symbols created by my own. Anything to do today if I want the code to be more like human-written?

I think why the code is too wide is because that fn in the update-in function and another fn in this fn.
This looks really complex and there’s no way to beautify without changing the code IMO.
I would like to extract the update function to somewhere outside this read-next function.

1 Like

That is ideal formatting for that code, but there is way too much code in that function. If you broke out the (update :messages ...) form into its own function, you’d cut the drift in half. Also, rather than write

(update
 :messages
 (fn [messages]
   (assoc
     messages
     <<args>>))

You can write

(update
 :messages
 assoc
 <<args>>)

since the existing value gets passed as the first argument to assoc. But in this case, it looks like what you really want is assoc-in:

(assoc-in
 [:messages op-id]
 (merge...)

That can replace your entire (update :messages) form.

You can write incredibly elegant code in Clojure, but that depends on taking the time to break things down into small functions and macros. You could write that code in anywhere from one function up to perhaps six, depending on how OCD you are and how much code you intend to reuse. I write a ton of small, slightly nested functions both because I like how the code looks and because it leads to really easy code reuse and composability. Good programming patterns naturally lead to elegant code in Clojure.

3 Likes