9 ms·
Better Clojure formatting
- harlanji 8y agoNo mention of cljfmt, which I’ve used for a few years via lein plugin (‘lein cljfmt fix’).
- jmlsf 8y agoIt's worth pointing out that this post is in response to the discussion over at clojureverse, which is an effort to come up with an option-free indentation standard similar to gofmt or prettier. https://clojureverse.org/t/clj-commons-building-a-formatter-like-gofmt-for-clojure/3240 https://clojureverse.org/t/clj-commons-building-a-formatter-...
- enoch_r 8y agoI think you're missing the point. The point isn't "no tool exists to format my clojure code!" which is obviously wrong. The author is looking to define a standard that is simple enough to define once and be used universally. `cljfmt` is awesome and I use it myself, but it fails on pretty much all of the criteria the author lays out (slow startup time, it is "extensible," formats macros/binding-forms/functions differently, etc).
- TeMPOraL 8y agoHonestly, I think this is a bad idea. The way Lisp works, you need to load the code to have a shot at knowing the correct indentation. You can't just do it statically without abandoning the different syntax for function calls vs. block-of-code macros, as the distinction between the two may not be generally apparent until runtime.
- deleted 8y ago[deleted]
- enoch_r 8y agoThat isn't "how lisp works," that's how most people choose to format their lisp code. And this is the entire point of the article. He discusses this at length: to reach the universality and speed of a `gofmt`-like formatting tool, we would have to give up on the idea that s-expressions should be indented differently based on their content. Personally I find this a very tempting tradeoff. You may not, but indenting different s-exps in different ways based on their first element is not "correct," it's just... what most people choose to do.
- lispm 8y ago> You may not, but indenting different s-exps in different ways based on their first element is not "correct," it's just... what most people choose to do. They are not just s-expressions. They are programs. Just like a C code is not a random string, but a program.
- dmead 8y agoisn't it true that the lisp community is more tolerate of horizontal space than most subsets of programmer? That, plus the fact i haven't coded on a 4:3 monitor since ~2002 makes me thing this is not helpful. The overuse of newlines in coding standards is... a useless exercise.
- jetrink 8y agoI still like to constrain line length, because having two source files (or a source file and a REPL) side-by-side on the same monitor is so useful. The trade-off of avoiding long lines is a small price to pay. I recognize that most people like to have one file per monitor though.
- notduncansmith 8y agoI don't know about the wider Lisp community, but I think Clojure is much easier to edit and more aesthetically pleasing when arranged in well-newlined blocks <=80 chars wide. Constraining line length is great for readability. The benefits are similar to those gained by limiting prose line length, and sentence length. Think of a long line of code as having similar traversal characteristics to a linked list. This requires more memory for comprehension, and is hard to search. Now compare this to a newlined block of code, which resembles a richer data structure that spatially colocates relevant elements. It's a difference that matters a lot.
- kazinator 8y ago> The benefits are similar to those gained by limiting prose line length, and sentence length. Not really; prose doesn't indent with increasing nesting, so line length in prose actually means that many non-whitespace characters to scan. > Now compare this to a newlined block of code, which resembles a richer data structure that spatially colocates relevant elements. Nobody writes huge lines in Lisps; but the indentation can get out of hand in large functions. So you have this neat block that spatially colocates relevant elements, but has 150 columns of whitespace on the left, which creates a navigation challenge that is different from the problem of scanning a long line. If you combat this by simpy constraining the column length, then what happens is this problem (cramped expressions (butting up to 80 col limit)) You have little recourse but to break it up into smaller functions (perhaps some of them nested in the parent).
- bunderbunder 8y ago> if everybody is reformatting it’s doesn’t really matter how much. Really aggressive reformatting introduces git blame horizons that can make code archaeology more annoying. I wouldn't consider it a reason not to switch to autoformatting, but it might be a reason not to get too wild about changing the conventions.
- hinkley 8y agoIt's why I issue a separate commit for reformatting. At least when the person sees my commit in git blame they know to annotate the previous version.
- notduncansmith 8y agoOr even better, do it once for the main codebase in a single commit, then make sure developers have their editors apply the formatting on save, and a formatting check in CI, so that way misformatted code never gets code-reviewed.
- napsterbr 8y agoThe 80-character limit may have come from punchcards, but it's still important in 2018. It allows me to have three side-by-side columns of code, and I know I'm not alone. Even 100-character limit would break similar setups. Furthermore, I personally find a lot easier to read code that is not too spread horizontally. I do not have extensive experience with Lisp or Clojure, but all languages I've worked with will handle a "small" line width of 80 character just fine most of the time. I do recognize this is a matter of personal preference, but please don't dismiss the 80-character limit because it's old. It may be, but it is still relevant. Other than that, I wholeheartedly agree with the post. I started toying around with clojurescript for the past few months, and the one thing I can't wrap my head around is its formatting. Sometimes one space, sometimes two. Sometimes block spacing. It's very confusing.
- gizmo385 8y agoWe expanded our team line limit to 88 characters and that feels quite a bit better than 80. There are a lot of pretty common lines of code that once you're nested in a function/if statement/loop stretch out beyond 80 characters but fall comfortably into 88.
- slifin 8y agoI personally like that it discourages nesting Changing your standard to a value so close to the existing standard seems like an odd choice are you using a verbose language?
- cbcoutinho 8y agoI think the python formatter 'black' came to the same conclusion - the default line width is 88 characters instead of 80
- dkersten 8y agoI used to use 100 characters as my limit and it was pretty nice. I switched back to 80 because most editors already mark the 80 character point and I didn't want to have to configure editors (basically laziness). I find 80 fine for most code. There are some cases where I naturally go over a little and its a bit clumsy to avoid it, but they're few enough that I don't care. Especially in Clojure code, I find that if things are that long, then its too nested and hard to follow anyway and I rearrange my code (use ->, ->>, let etc to split nested forms up). In Clojure code, the most likely reason to go over 80 characters for me is let forms like (let [long-descriptive-names-that-get-too-close-to-80 (and we-have-hit the-limit)]) but overly long names mess the code up anyway and should be avoided. Descriptive is good, but too long makes it hard too read too.
- lilactown 8y agoI agree with everything he said... except the 80 col limit. I know I'll probably lose this fight, but I frequently write code on all sorts of different screen sizes broken into split panes (Code | REPL, Code | Browser, Code - REPL | Docs) and having a line limit makes this manageable. Maybe 80 isn't the right one, but letting code stretch out to ad-infinitum (either with awkward line breaks or having to scroll) is a big ergonomic loss for me personally.
- ajuc 8y agoI agree, and it's not mainly because of splitting the screen. Very long lines of code just read badly, even if you have the screen-space to fit them. It's easy to skip and forget where you were when you look to compare the code to something and then look back.
- dkersten 8y agoYep, there's a reason most article/blog sites use a fixed-width column of text. Long free-flowing lines of text are simply hard to read. I also used to not care about side-by-side, but now, I find it too useful. Besides, nobody wants to scroll to read git diffs.
- kjeetgill 8y agoI usually follow 80 if possible, or in Java 100, but put a hard limit at 120. There's just a need for the occasional judgement call. This seems to work well enough for two columns. Useful for editing, but indespnseble for diffs. For reference my 15" Mac has 234 columns at full screen.
- lincpa 8y agoThere are some professional domains where the "domain-namespace-alias/domain-variable-name" is over 80 characters. Clojure should not enforcement language-independent aspects.
- thomastjeffery 8y ago> > Avoid trailing whitespace. > This is one of the stupidest things to automate. Unlike indenting, removing trailing whitespace simply produces diffs where it doesn’t matter. And does nothing more. I suppose formatting is a weak point for version control systems, but I think that is all the more reason to get rid of trailing whitespace. The more your code is clean of ambiguous formatting, the less it will change between commits.
- dm3 8y agoExactly! Just separate the commits which fix formatting from the ones that change code.
- Guthur 8y agoIf you got rid of it from the beginning there is no diff. The argument makes no sense.
- kjeetgill 8y agoIn vim it's as simple as :%s/\s* $// (okay, not that simple if you don't spend time in regex or in the shell) and git can hide whitespace only changes in diffs. Just don't check in trailing whitespace. If you need to clean up a whole codebase you can throw in something like find -name * .java | xargs sed -i 's/\s* $//' and just commit that once. Sidebar: How do I print an asterisk and a char without adding in whitespace.
- kbp 8y agoTrailing whitespace also makes jumping to the end of a line or moving back over a newline not take you where you expect, which is annoying when it breaks your flow. It's only ever a mild nuisance (in that scenario or for diffs), but the fact that it's trivial to automate seems to make it a no-brainer to me.
- vseloved 8y agoit's a classic "worse is better": if doing good is hard, let's do the easy stuff. meh
- vbuwivbiu 8y agoJeez it's just data! Use a standard format for serialization and then use programs to format it for reading & everyone gets to read it the way they like it
- mrmaloke 8y agoThat way you force a serialization standard upon everyone. I'd rather force a formatting standard upon everyone and get the benefit of readable code with any tool e.g. also code reviews in version control. There's hardly a way to read code "the way I like it" in a webapp.
- vbuwivbiu 8y agothat's exactly what I mean by standard serialization format - format of code exchanged between people and programs. You can always rely on the standard way (for example the one presented here) when reading code on github or when you open it in your text editor by default, and then if you have another way you prefer that it be formatted, you can use a program to format it that way for you while you work on it - however when you commit it, it's formatted the standard way not your special way. We've conflated how the code is stored with how it looks. We need to separate these. That's one of the problems with equating code with text: they're different things. Code is data (or objects), text is a way to represent code.
- rogual 8y agoI don't know if they are totally separate. Say you like to see XMLHttpRequest and I like to see xml-http-request. If I type xml-http-request, how can a piece of software know how to translate that to XMLHttpRequest? It's not just variable casing; it's any situation where one coding style distinguishes two things which look the same in another coding style. And I think there's a lot of such cases.
- vbuwivbiu 8y agothis is in the context of LISP code
- agumonkey 8y agoThat's one very nice side of python, no time wasted on formatting. I cannot bear formatting arguments even for a second it kills my soul.
- dkersten 8y agoI've heard endless Python formatting debates too. Luckily most people have settled on pep-8, but even there, there are points of contention, such as line length limits, or how spaces won the tabs vs spaces debate (despite that imho tabs are superior as they encode indention cleanly: 1 tab = 1 indent, yet each person can set their editors tab width however they prefer) See, nobody will ever fully agree on formatting.
- agumonkey 8y agoThat's true.. even though I wouldn't consider variable naming and docstrings a formatting debate.
- dkersten 8y agoTo me anything that affects the visuals of the code without the semantics is a formatting issue -- so tabs vs spaces (and how many) for indents, space after function names (before parameter lists), space between operators, whether camelCase or something else, Foo.foo or Foo.Foo or foo.foo or..., etc. They're all formatting. But that wasn't my point really, which was just that despite its forced indentation, Python still has, in my personal experience, its own formatting debates.
- deleted 8y ago[deleted]
- TeMPOraL 8y agoI see a fifth column growing here. The core problem (sort of created here) is about how lispy Clojure should be. Unlike most programming languages, Lisp code isn't meant to be fully statically analyzable. This affect indentation as well, because the "full picture" of syntax is only available at runtime. Consider a macro invocation: (foo bar (some args) (some other code)) vs.: (foo bar (some args) (some other code)) Which indentation style is the correct one? That depends on what "foo" is. If it's a function, then the first one. If it's just a regular macro, it's still the first one. But for some particular macros - like defun in elisp/CL, the valid syle is the second one. You can't, in general[0], know that until runtime. That's why the Lisps usually give you tools for that. In Common Lisp, you have &body, which works like &rest but also hints that the expected indentation is like the 2nd case I shown here. In Clojure, you have metadata - you can attach e.g. {:style/indent 2} to your macro, which signals that desired indentation changes to 2nd form on third argument. My personal opinion - most Lisps are designed for interactive, in-image work. So is Clojure. If you want to have a completely static formatter, go back to coding Ruby or Python. -- [0] - You can guess it in simple cases, but not when you're dealing with macro-writing macros, i.e. code that writes code that writes code.
- zimablue 8y agoIs there no way out, to run the formatter after the macros?
- TeMPOraL 8y agoYou've already made the crucial step then - you've loaded the code. The author apparently wants to avoid that. You could get away with parsing the code statically in the most trivial cases, but to do it even remotely correctly, you'd have to parse all the code, and only then format individual files. A lot of work for no good reason, and it wouldn't save you from macro-writing macros. In a large Lisp project, this would screw a lot of things up. I don't have relevant Clojure example here, but in Common Lisp, I worked on a pretty big codebase (CLIM2), where trying to indent things without loading the code into a Lisp image would screw up indentation in 50+% of the files - because a lot of frequently-used macros were written by other macros.
- alexeiz 8y agoClojure doesn't need this gofmt crap from Golang. The reason Golang needs it is that it uses a bizarre code formatting convention of indenting with tabs and aligning with spaces. It's impossible to correctly use both tabs and spaces in your code manually. You're bound to screw it, so there is a need for an automated formatter that enforces this style. In Clojure we format everything with spaces (https://ukupat.github.io/tabs-or-spaces/ https://ukupat.github.io/tabs-or-spaces/ - 99% of Clojure code uses 2 spaces). This formatting style is pretty much trivial and all code editors support it. I'm perfectly fine with Clojure code formatted slightly differently depending on a person who writes it. If you want a BDSM language, code in Golang.