Rich Comment Blocks

The three main types of comments in Clojure are

The last one, the Rich Comment, is a pretty cool feature of Clojure.

Comment

The first type of comment is a literal comment

; I'm a comment

Anything that follows the ; is ignored by Clojure until the end of the line. A common use for a comment is to add human readable documentation to your code to help the people reading our code understand it. We can also comment out blocks of code which we don't want our program to run.

Discard Comment

The second type of comment is a discard comment

(-> 5 inc inc inc)     ; 8

(-> 5 inc #_ inc inc)  ; 7

The #_ will comment out the form directly behind it. In the above, the code commented out is the second inc.

I actually ignored this tool for an embarasignly long time, but it's wildly useful and actually makes coding faster!

At a high level, what makes the discard comment great is that it doesn't return a value, they nest and you can insert it into the middle of a line of Clojure code and it only comments the form directly behind it.

Additionally, if you're commenting out an entire block of code you only have to add the discard to the top level of the form and the entire form will be commented. This means the formatting of the code block won't be affected and you have very little code to remove.

Here's an example of nesting:

(-> 5 #_ inc #_ inc inc) ; you could discard each form in turn
(-> 5 #_ #_ inc inc inc) ; or you could stack them

Here are some examples of where you might find the discard comment useful:

(or #_ (int? 2) (nil? "Thomas"))

(let [my-number 5
      #_ #_ another-number 13]
  ;...
  )

{#_ #_ :name "Between Two Parens" :host "Thomas"}

The second take away is that you don't have to add spaces after the discard comment:

;; these all produce the same result
(-> 5 #_ inc #_ inc inc) ; space
(-> 5 #_inc #_inc inc)   ; no space

(-> 5 #_ #_ inc inc inc) ; space
(-> 5 #_#_inc inc inc)   ; no space

The difference between adding the space or removing the space is which one you find more readable.

Rich Comment

Finally, we have the comment macro which is more affectionatley known as a Rich Comment Block:

(comment
  ; everything in here is ignored...returns nil
  )

The first time I heard of a Rich Comment was in Stuart Halloway's excellent talk Running With Scissors where he notes:

Yet, even after watching Running With Scissors the use of the Rich Comment hadn't started to click yet. Two more things would need to happen: The first, I would witness REPL Driven Development used in person by David Nolen. The second, I would start to use REPL Driven Development in my own workflow. When I did these things, I was able to better see the benefits of the comment macro as

  • documentation
  • a save point
  • code setup
  • improved code exploration
  • preservation of syntax highlighting

With that, let's review a few examples of the Rich Comment from real life Clojure codebases.

The first example illusrates the documentation and save point ideas.

(comment

(println (sh "ls" "-l"))
(println (sh "ls" "-l" "/no-such-thing"))
(println (sh "sed" "s/[aeiou]/oo/g" :in "hello there\n"))
(println (sh "sed" "s/[aeiou]/oo/g" :in (java.io.StringReader. "hello there\n")))
; ...
)

The above comes from the clojure codebase itself and is a code example of how to use sh. For me, the value is that we have an example of how to use sh (documentation) and we have some code ready for us to run through our REPL (a save point). This idea of having a save point becomes more powerful in the next example:

(comment
  (do
   (require '[my.app.db :as app.db])
   (require '[my.app.cart :as cart])
   (def db (app.db/connection!)))

  (cart/add db {:item-name "iPhone"})

The above builds on the idea of having a save point and layers on some code setup helpers. What the above does is add in a few lines of code which, when run, will provide us with a db connection. Through this, I can quickly begin interacting with my app's database code and building out features.

Of course, there are other types of setup code that you may want. For example, you might be working on a pure function which is just going to transform some data. In this case, we might setup a comment like this:

(comment

  (def xs #{{:a 11 :b 1  :c 1 :d 4}
            {:a 2  :b 12 :c 2 :d 6}
            {:a 3  :b 3  :c 3 :d 8 :f 42}})

  (def ys #{{:a 11 :b 11 :c 11 :e 5}
            {:a 12 :b 11 :c 12 :e 3}
            {:a 3  :b 3  :c 3  :e 7}})

  (join xs ys))

The above is the example Stuart provided in his talk which provides us with some sample data allowing us to immediately begin using our functions to transform said data.

Conclusion

These are just a few examples of how to use a Rich Comment Block. The most interesting part of the Rich Comment Block for me is that it's a tangible example of the pragmatism of Clojure. In this case, the comment macro provides an additional mechanism for speeding up my workflow and making our code more maintainable overall because of the improved documentation and context we get from these comments.