Cond indentation/readabliity, continuation syntax proposal for expr

Hi! Wanted to get some feedback on an annoyance and possible fix for cond and similar forms…

I frequently encounter/write code that runs into line length/spacing issues. I think this impacts most of us, whether you prefer 80 chars line max or 120 or whatever. Example:

  ;; Typical nice case; alignment helps
  (cond
    (short1)       (short2)
    (still-short1) (still-short2)
    :else          (short3))

  ;; Problematic longer case; lines can get VERY long
  (cond
    (some-really-long-call :to "whatever")       (do-something-also-long)
    (some-other-thing :with "even" :more "args") (some-other-action)
    :else                                        (the-last-possible-thing))

  ;; Break into newlines, but hard to tell TEST from EXPR lines! (what I usually do)
  (cond
    (some-really-long-call :to "whatever")
    (do-something-also-long)
    (some-other-thing :with "even" :more "args")
    (some-other-action)
    :else
    (the-last-possible-thing))


  ;; Readable and typical, but just blew up from nice 3 lines to 9; big problem IMO
  (cond

    (some-really-long-call :to "whatever")
    (do-something-also-long)
    ;; Maybe a comment here to separate, or just another wasteful blank line
    (some-other-thing :with "even" :more "args")
    (some-other-action)

    :else
    (the-last-possible-thing))

  ;; How about this??
  (cond
    (some-really-long-call :to "whatever")
    #_> (do-something-also-long)
    (some-other-thing :with "even" :more "args")
    #_> (some-other-action)
    :else
    #_> (the-last-possible-thing))

Thoughts on the #_> comment/indicator? Any big problems you could see it causing? Any alternative you like better? It works well with Emacs’ default clojure formatting, and also cljfmt (should try with zprint and other editors). It also looks pretty good with a ligature/unicode substitution (though I don’t think these arrows are the ideal substitution), like:

Screen Shot 2021-07-06 at 11.12.39 AM

We could come up with some other macro like #> but maybe not worth it since #_ works for cases I can think of, and is still short.

This should also probably apply to case, condp, cond->, and maybe others. It’s even almost nicely congruent with condp's :>>.

Here’s the Emacs setup (thanks to Malabarba!) that makes the pretty arrow (I assume can be done similarly for other editors):

(global-prettify-symbols-mode)
(setq prettify-symbols-alist '(("#_>" . (?\s (Br . Bl) ?\s (Br . Bl) ?\s
					     (Bc . Br) #x21a6 (Bc . Bl) #x21a6))))

(Yes, we could change all our editors and tools to indent the EXPR line by a couple chars, but I can’t imagine getting teams to agree how to do that. This proposal alone may also be difficult to get people to agree on, but at least it seems like a harmless, optional convention that doesn’t break anything.)

If I have long conditions and need to have my cond formatted as alternating lines of condition/expression, I just add blank lines between each pair to add readability. I haven’t found anything else that is both simple enough and readable enough to be worth the effort of introducing a custom convention.

5 Likes

I agree that having to introduce a convention is not ideal. Though I’m considering trying it.

My first approach to this was trying to get my editor to do some sort of overlay (and not need any special comment addition) to make the TEST line stand out, but I’d need to do some research to get it to work (if even possible), like:

Screen Shot 2021-07-06 at 12.00.26 PM

Actually, I think a § (section sign) or (pilcrow) is a better/good symbol than the triangle.

1 Like

I think in terms of code, it shouldn’t cause issues that I can think of.

Now as too if I like it better than proper indent or spaces not sure.

You could also try with a comma:

(cond
    (some-really-long-call :to "whatever")
    , (do-something-also-long)
    (some-other-thing :with "even" :more "args")
    , (some-other-action)
    :else
    , (the-last-possible-thing))

;; Or

(cond
    (some-really-long-call :to "whatever")
    (do-something-also-long)
    ,
    (some-other-thing :with "even" :more "args")
    (some-other-action)
    ,
    :else
    (the-last-possible-thing))

;; Or

(cond
    (some-really-long-call :to "whatever")
    (do-something-also-long),
    (some-other-thing :with "even" :more "args")
    (some-other-action),
    :else
    (the-last-possible-thing))
2 Likes

Thanks @didibus! The comma is pretty good and simple. I definitely prefer (and I think like) your first example. It’s a little trickier to create a “pretty symbol” for, but maybe it’s simple enough that it does the job and doesn’t need a glyph. Could even make it a double-comma.

Oh but alas, cljfmt is pretty particular about commas and does not like it (even though Emacs treats it as expected).

What does it do with it?

It treats commas as whitespace, so ignores them, and then complains that the EXPR lines should not be indented. Looks like using :remove-surrounding-whitespace? option does not affect this.

 (cond
   (some-really-long-call :to "whatever")
-  , (do-something-also-long)
+  (do-something-also-long)
   (some-other-thing :with "even" :more "args")
-  , (some-other-action)
+  (some-other-action)
   :else
-  , (the-last-possible-thing
-     :foo))
+  (the-last-possible-thing
+   :foo))

Ah, interesting thanks.

Cursive moves commas to the end of previous line when reformats code.

Feels like adding complexity to anyone reading the codebase. It could easily lead to questions like

Is this a new form of reader macro added onto the language…

Where is this macro defined, is it on the project or in a library that was imported.

Introducing additional ‘convention’ that can easily be mistaken for syntax seem to add complexity

A blank line does not suffer this complexity

3 Likes