Rich Comment Blocks in Clojure
betweentwoparens.com
betweentwoparens.com
Most languages operate on code as text. When commenting out code with //, it's easy to mistakenly leave out a line containing a closing bracket for example. When using /* , you have to manually insert the closing * / at the exact right spot.
On the other hand, #_ simply operates on the logical form that follows it, instead of operating blindly on characters. You know it's going to do the right thing whether you want to comment a long multiline form, or a single function call in the middle of a line.
Using structural editing (Paredit) to navigate and reorganize code is an incredible experience for the same reasons.
I'm one of these people that doesn't feel "flow" or get in the "zone" when working (leading me to sometimes wonder what I'm missing out on, or if I'm lacking something, although my career is fine so far). But writing Clojure is probably as close as I can get. :)
//if (some logic) {
do something
//} else {
// something else
//}
Or in other cases collapse two if/else blocks together, or stuff like that. It's not as common as commenting out a logical unit, definitely, but it's far from uncommon.How would you achieve that in Clojure? Seems like you'd need to copy/paste, like this?
#_(if (cond) thing other_thing)
thing ;(if (cond)
thing
; other)
If yes: great! The best of both worlds. (if (test) (then) (else))
(if true #_(test) (then) (else))
(if false #_(test) (then) (else))
makes it quite simple to leave the tests in. Also, quite easy to raise forms (structurally raise) and reevaluate the form and then undo but not reevaluate the form to have it modified. (defroutes data-routes
(GET "/something1" [] (sample-json-data))
(GET "/something2" [] "hello1")
(comment
(GET "/something3" [] "hello3")
(GET "/something4" [] "hello4"))
GET "/something5" [] "hello5")
)Then when a client tried to access /something5 , I began getting null pointer execptions. This took me a while to figure out what I had done wrong.
#_(GET "/something3" [] "hello3")
#_#_#_#_GET "/something3" [] "hello3"
if it strikes your fancy. The possibilities for bikeshedding here are truly remarkable.; and ;; pretty usage is same as Common Lisp. #_ is replaced with #+nil in Common Lisp which is just a reader macro. ;;; is usually reserved for prose, and of course there's #| commenthere |# which is just another reader macro, and is suitable for code examples, where you can probably still eval it in your text editor.
(defmacro comment
"Ignores body, yields nil"
{:added "1.0"}
[& body])
A macro that takes any number of arguments and does absolutely nothing. This is still a macro invocation, so the arguments must not cause a reader error.It is a common pitfall to write non-readable content in a comment expression, which can certainly be confusing to newcomers who are used to less stringent multiline comments like /* ... */ or triple-quoted strings in Python.
It's a gotcha, but in the end I think it's neat that it makes sure it's valid.
Rich comments are not just the equivalent of a multiligne comments in other languages, I expect the statements inside of it to be run, often many times, during normal clojure development. I don't want to have broken code inside.
(defn prime?
"Returns true if given number is prime."
{:example
'((prime? 97) "=> true"
(prime? 74) "=> false")}
[n]
,,,
;; implementation
)
(-> #'user/prime? meta :example)
;; => ((prime? 97) "=> true" (prime? 74) "=> false")
You can even incorporate assertions and have tests that would make sure the examples are correct, something like: (defn prime?
"Returns true if given number is prime."
{:examples
'((-> (prime? 74) false? assert)
(-> (prime? 97) true? assert))}
[n]
,,,
;; implementation
)
(doseq [f (->> #'user/prime? meta :example)] (eval f))https://github.com/clojure/clojure/blame/4ef4b1ed7a2e8bb0aaa...
Of course I might be missing some obvious advantages but it seems to me that it is better to invest the time in turning these comments into tests rather than having them thrown randomly in source files.
I mean eventually all these comment blocks are just test cases aren’t they? We use them to evaluate expressions and compare the output value to our expectations. When I want to explore I build a small test case and use the fast feedback loop to have it as interactive as possible. Would be happy to hear any thoughts on this.
Much like a "save point" in video games.
It's not about actually storing state, which Lisp with image-based development can do in some form.
Using 'comment' is cool, I hadn't used in in my previous Clojure life, but as HN manishsharan said here, putting macro expressions inside 'comment' can cause problems.
It feels good to be back in the Clojure community but I also have to admit I miss not using Common Lisp every day.
I think `comment` is nice if you have an editor that doesn't syntax highlight and let you structurally edit discard comments. But in my Emacs setup, code that is after a discard comment still gets all highlighting and syntax editing and evaluation as any other code, so I don't feel a need to ever user `comment` macro over it.
It is advised that once things stabilize you take your learnings and stuff them into a unit test for posterity.
This is just a way to achieve that. However, combined with some of the other clojure niceties (namely cider and structural editing) it does make it a better experience for me.
It allows me to maintain a better flow as I can maintain REPL state if needed(especially if it's a quick hack where dependency injection like component isn't worth the overhead). When I'm done exercising the code I can just make minimal changes to make the comment a test, leave it in place, or move it over to a bag-of-tricks user.clj.
Homoiconicity of Lisp allows you to evaluate any expression or sub-expression without any kind of ceremony. Other languages do not have the luxury of giving you the answer right away. There's always some "ritual" involved, automated or not, but it is still required, but in a Lisp connected to a REPL, you immediately get the answers.
The ambiguity is there for a native speaker, technically, but it's unlikely to be triggered. It never occurred to me to interpret it that way.