What do folks suggest as a standard formatting tooling setup for a team?
We’re working on a large codebase that’s been mostly written using IntelliJ/Cursive, but we now also have people using Vim and Emacs. From what I understand, Cursive has a built-in linter/formatter, as well as a specific commenting style (single semicolon comments everywhere), and this has become the defacto standard in our codebase. Each dev environment has a different setup though, so we’re having large diffs which are basically one form shifted two spaces to the left, or similar.
In my own projects I’ve been using @chrisoakman’s excellent standard-clojure-style-js but it doesn’t work here because it’s not configurable (by design) and doesn’t match the cursive style. What are my options? cljfmt? zprint?
I think the best solution is that standard Clojure is supported in intellij as it is the most minimal of all formatters. It was stated above that formatting doesn’t matter that much. I’d say, it should cover things like removing extra spaces, fix miss aligned keys in a map (miss alignment caused by extra space on one of the keys for example), remove extra empty lines, (maybe) arrange namespace imports Horizontal scroll limits I think are optional if the devs have common sense. I never saw Clojure code lines past 120 chars tbh - to me this is a non issue but if you start a discussion about it in order to arrive at a mandated max char Len you usually end up with a lengthy discussion that doesn’t give good results
We had the same situation at Sharetribe. Fairly large codebase, mostly Emacs users but also IntelliJ/Cursive and VSCode/Calva users.
A couple of months ago, we decided to start using standard-clj. Everyone runs it locally before pushing, CI doesn't check formatting (IMO it's unnecessarily strict to make CI fail because of formatting)
It's been working fine but there are couple things that have made us wonder if we should change to something else.
Personally, I'm not a fan of the https://github.com/clj-commons/formatter/issues/9#issuecomment-446167649 in standard-clj makes. I'm a happy user of Emacs and aggressive-indent-mode (indentation is kept correct while I type), and since https://github.com/oakmac/standard-clojure-style-js/issues/194 it caused a lot of unnecessary whitespace changes when the code uses argument alignment (according to Rule 3) and then aggressive-indent-mode changes it to fixed two-spaces alignment. We fixed this so that we decided to run a https://github.com/sharetribe/standard-clojure-style-js/tree/format-without-rule-3, which formats without Rule 3, and decided in the team that we'd always use fixed formatting. This almost solves all the indentation issues, except for the namespace (:require ,,,) forms, which are formatted with a single space, unlike any other list in code.
Just a couple of weeks ago, we introduced clojure-lsp for our tooling for linting, and what I'd love to try out next is to use it for formatting and see how that works. It uses cljfmt under the hood, which I haven't used before.
@rap1ds Can't you use zprint instead? standard-clj is kind of a no config by philosophy JS formatter for Clojure, can't imagine running a fork of it to "customize" formatting makes sense.
@rap1ds I also don't understand how it creates unnecessary commit diffs? If you said you run standard-clj formatting pre-commit, than it should reset the formatting to it and that Emacs had temporarily changed it shouldn't matter no?
One thing I think zprint is best for the use case of pre-commit is that you can have it fully reformat where others are simply re-indent. Full reformat means even custom newlines, extra spaces and all that are reset to the standard.
@rap1ds Actually I take it back. I'm not a fan of the deviation from Tonsky's simple formatting, and Rule 3 is 💩 🤣 My favorite part about Tonsky's minimal formatting was just going super simple with form indentation. If I am going to go "dumb" indentation I'd like it super simple.
@didibus Sorry, I didn't explain myself clearly. Here's a step-by-step example: 1. Let's say I have a file like this:
(ns rule3-example)
(and true
true
true)
2. And I have configured my Emacs to use fixed indentation with the following in my .dir-locals.el
((clojure-mode
;;
(clojure-indent-style . always-indent)
(clojure-indent-keyword-style . always-indent)
(clojure-enable-indent-specs . nil)))
3. Then, I ran standard-clj fix rule3_example.clj. This doesn't change the file, because the indentation is correct, according to Rule 3.
4. Now, given I have aggressive-indent-mode enabled, and I set my cursor after the second true:
(ns rule3-example)
(and true
true|
true)
5. And hit Enter and add a false value, this is what I got:
(ns rule3-example)
(and true
true
false
true)
6. If I now run standard-clj fix rule3_example.clj, it doesn't change the indentation because this is correct according to Rule 3.
The result is that the indentation has changed from argument alignment to fixed 2 space indentation. This is not standard-clj's fault, this is due to the fact that AFAIK there's no way to configure Emacs and clojure-mode to understand Rule 3.
So after taking standard-clj into use and formatting our whole code base with it, we started seeing a lot of PRs with plenty of unnecessary whitespace changes like the one described above.
Thus, what we did is we forked the standard-clj to ignore Rule 3 and formatted our codebase with the forked version to get consistent fixed indentation. Then we decided in the team that we're not using argument alignment, only fixed indentation. And now we've been free of unnecessary white-space changes :) I hope this clarifies what I meant!
> Can't you use zprint instead?
Hmm. I haven't tried it, maybe I should!
> standard-clj is kind of a no config by philosophy JS formatter for Clojure, can't imagine running a fork of it to "customize" formatting makes sense.
It absolutely does not make any sense and goes fully against the philosophy, I get it :D But we just needed a way to format the codebase with consistent fixed indentation, and since it's a "no config" formatted, there's no --disable-rule3 configuration either, so, what can you do 🤷 But, this was only one-time run. After that, we've used the unforked version for our daily formatting
> If you said you run standard-clj formatting pre-commit, than it should reset the formatting to it and that Emacs had temporarily changed it shouldn't matter no?
Noup, because or Rule 3, this doesn't happen. Rule 3 allows both styles.
I 100% agree with you about Rule 3. I see a lot of value in Tonksy's minimal formatting and I think standard-clj would be much better without Rule 3.Ya I see, that makes sense. Rule3 itself is responsible for the odd behavior because it dynamically aligns. This is where zprint can be better. Because it'll reconstruct the full alignment if configured to do so. Though it can also be annoying because you have no control, like if you want 3 spaces between something it'll reset it back.
at my last job, we used zprint and failed CI on formatting mistakes, but after working there for 2 years, i found it to be mostly a pain without almost any benefits. frequently, we ran into situations where code looked really weird without recourse, and i personally spent a lot of time adjusting the zprint config to make those situations less weird
after all that, i think formatting is relatively unimportant and everyone should change their editor to not format on save.
Hmm, that’s definitely a way to go. Thank you for the perspective, Noah!
I'm definitely with Noah on this, with the caveat that at minimum you pick a max line length so devs aren't horizontally scrolling through code (and if you don't want to pick one, flip a coin between 100 and 120).
FYI - I know that Colin wants to support Standard Clojure Style in Cursive. And it is a goal of Standard Clojure Style to “meet you where you are”, so I also want to help make this happen and be easy to use.
Where I work, we don't format, and there are several files I have to literally scroll across to read everything, and I'm using an ultra wide display.
I started work on a pure Java implementation of Standard Clojure Style, but it is not complete / ready to use yet. https://github.com/oakmac/standard-clojure-style-java
I'm in the exact same situation, there's a few small emacs things I've been able to do that seem to match everything intellij does exactly but haven't tested it comprehensively across the entire codebase.
clj-fmt is good enough. If you don’t want to be annoying just make ci apply formatting (ex upon merge in main) if it diverges with whatever standard you set, that’s mostly pain free this way.
That or just don’t. Basically as soon as it forces people to all converge to a specific style from their side it will start holy wars in my experience.
Cljfmt, cljstyle or zprint seem to be the most commonly use tools in my experience. standard-clojure-style is an interesting idea but i don't think its that standard in terms of use (as its very new compared to other tools). I use cljstyle as it has a really nice diff output https://practical.li/clojure/clojure-cli/clojure-style/
@gosha I do plan to support standard Clojure style in Cursive, as well as a fully-cljfmt compatible version. But Cursive's built in formatter is already quite configurable - if you have specific cases which are problematic, I can help you configure them in Cursive. If you'd like help with this, probably best to bring it over to #cursive.
@nbtheduke I don't understand how it was failing CI? Does zprint have a formatting validation mode? Otherwise why didn't you set it up so it auto-formatted pre-commit ? Then it would never fail and PRs would never have formatting diffs
not everyone sets up pre commits, and sometimes suggested changes in github would affect the code in unexpected ways
By the way, in Emacs indentation is handled by ClojureMode itself, no use of zprint or cljfmt. I think this differs from VSCode Calva which uses cljfmt I believe.
What I've always done, is run formatting as part of the build. So not formatting before sending a PR is the same as not having tried to run the tests before submitting a PR. Everyone is expected to run the build command before opening a PR. If not that's the first PR comment you leave for them to fix.
I think you can also set up branch protection rules so a PR can't be merged if it fails certain things, like some "build command".
What exactly is case* and should it be listed in the special forms doc?
https://clojure.org/reference/special_forms
Several of the special forms have implementation bits that are marked with foo* that are directly understood by the compiler. These should be considered part of the implementation and usually not used directly
Okay so if I'm interpreting Clojure, the guidance would be to implement Scratch that, I have a better way to do this.clojure.core/case myself and not macroexpand it or gain knowledge of case*, does that sound right?
Regardless, this context helps. Thanks to both of you.
This is just a fun impractical idea that's not worth your time. Raku language's rakudo compiler is written in NQP which is a higher-level language designed for compilers. NQP compiles to MoarVM bytecode. In theory, there can be a clojure implementation that targets NQP. Like clojure, raku doesn't yet have tail-call optimization. Raku has a lot more advanced OOP machinery than java. Clojure is a good fit for raku although clojure's OOP capability isn't as detailed as raku's. Raku is a complex language. Since raku is merely a niche language, writing a clojure implementation for NQP is entirely useless, but it's a fun idea regardless. Python is actually important as a platform, Basilisp(clojure on python) should get more time, attention, and resources. According to TIOBE index, python is the king. Basilisp can potentially replace babashka although basilisp's startup time will not be ultra fast. Python is good for GUI applications, system scripts, AI, LLM, data science, etc, ...
https://pypl.github.io/PYPL.html is another popularity index for programming languages. Python is also the king there.
https://www.statista.com/statistics/793840/worldwide-developer-survey-most-used-frameworks/ says python libraries are the most used in the world.
https://www.statista.com/statistics/793628/worldwide-developer-survey-most-used-languages/ says python is the third most used language in the world.
Python is king.
I think you missed my point. That "most used", "most tutorial searched", "most installed" is all moot, and result in things like Scratch, a literal programming language for children, being ranked higher than Rust.
That be like saying that tuktuks are the king of cars because they're the most popular, used, purchased, etc
Sorry, just wanting to also make clear that I think Python is pretty good and clearly dominates certain use-cases. And for that I'm very happy someone is trying to host Clojure on it. What I mean is those ranking all have an underlying criteria, it's not possible to compress languages on a single axis. Python will rank near/at the top in places and others will in other place, all depending on your criteria.
That's why Clojure is pretty awesome, because by having multiple supported runtimes for Clojure you can likely reach every place you'd need to go. And because of that, I hope we continue to see Clojure JVM, Clojure CLR, ClojureScript/squint, Basilisp, ClojureDart, Jank all thrive and continue to be well supported. I even wish we had more, Clojure for Raku for example!
Raku has been useless so far. Python isn't just popular. It is also strong for many use cases including system scripting, GUI development, AI, LLM, data science, and so on. System scripting can be done by almost any linux ninja. Many system applications have been written in python. Desktop GUI development in clojure has not been great so far. AI and LLM are becoming increasingly more important. I considered babashka and other scripting-oriented clojure implementations for system scripting, and none supports libraries. Basilisp libraries will/could be treated as plain python libraries.
If you don't hate writing in python, python is one of the most useful languages. If basilisp integrates with python module system properly, it can replace janet language for my system scripting use case.
Jank can start up faster than basilisp for system scripts, but it seems jank maintainer is not interested in integrating with linux distribution packaging systems.
> Raku has been useless so far 🤣 Wait, but why are you proposing that there could be a Clojure that targets it's runtime? I guess you did say "This is just a fun impractical idea". Is Raku not well supported? Is perl5 still the norm and people haven't moved to Raku?
I wrote a small application in raku and realized it is very complex for my simple needs.... I could have used janet which is vastly simpler than raku. Raku also doesn't have a big ecosystem. Raku doesn't start up fast for system scripts.
Janet has been my primary scripting language.
> Python isn't just popular. It is also strong for many use cases including system scripting, GUI development, AI, LLM, data science, and so on. Absolutely, no hate from me. I'm rooting for Basilisp. > I considered babashka and other scripting-oriented clojure implementations for system scripting, and none supports libraries. What do you mean by libraries? Babashka supports libraries, or you mean like linux package managed libraries?
Last time I checked, babashka didn't have installable libraries.
Installable libraries require a proper file system placement.
Python has installable libraries. Raku's module system is more advanced than python's, though.
It does support libraries, it's just not very many people make babashka libraries. Most time people make Clojure libs, and not many also work with bb, but some do. There's a list here: https://babashka.org/toolbox/
Can babashka libraries be installed like python libraries?
System scripts require system libraries. Python libraries can be installed as linux distribution packages. Raku libraries and janet libraries can be installed as linux distribution packages.
I don't want to drag bb.edn around for system scripts.
> Can babashka libraries be installed like python libraries? Hum 🤔 Normally babashka is it's own package manager. So you can do:
(require '[babashka.deps :as deps])
(deps/add-deps '{:deps {some.lib {:mvn/version "822.2.1109.0"}}})
Like inline inside your script to include libs. Or similarly you can define them in bb.edn
Which isn't what you want I assumeThis is why janet is my system scripting queen.
> I don't want to drag bb.edn around for system scripts.
Well you don't have too. For example you can just intall bb and then run:
#! /usr/bin/env bb
(require '[babashka.deps :as deps])
(deps/add-deps '{:deps {coldnew/left-pad {:mvn/version "1.0.0"}}})
(require '[coldnew.left-pad :refer [leftpad]])
(println (leftpad "Hello, world!" 20 "-"))
The script will dynamically install the dependencies it needs as it runs (that's what the deps/add-deps does).maven? Does it pull JVM dependencies?
This is what the above script did on my machine:
> ./fun.bb
Downloading: coldnew/left-pad/1.0.0/left-pad-1.0.0.pom from clojars
Downloading: org/clojure/clojurescript/1.8.34/clojurescript-1.8.34.pom from central
Downloading: org/clojure/data.json/0.2.6/data.json-0.2.6.pom from central
Downloading: org/clojure/tools.reader/1.0.0-alpha3/tools.reader-1.0.0-alpha3.pom from central
Downloading: org/clojure/data.json/0.2.6/data.json-0.2.6.jar from central
Downloading: org/clojure/tools.reader/1.0.0-alpha3/tools.reader-1.0.0-alpha3.jar from central
Downloading: org/clojure/clojurescript/1.8.34/clojurescript-1.8.34.jar from central
Downloading: coldnew/left-pad/1.0.0/left-pad-1.0.0.jar from clojars
-------Hello, world!
And on subsequent runs it just does:
> ./fun.bb
-------Hello, world!It can pull from maven, clojars, git, or a folder on your machine. The code has to be "babashka compatible". So either Java or Clojure libraries that use only the Java classes and Clojure namespaces included in babashka itself. Or some actual babashka library written in babashka.
You don't need a JVM though, it's all running inside Babashka, it's just Babashka can run a small subset of Java
I also write small system utilities in system scripting languages. That's not going to fly.
Well, maybe I take it back on java libs, I'm not so sure about pure java, but Clojure one ya. Like this is the lib I used in the above script: https://github.com/coldnew/left-pad.clj
I want to start with a script and deliver it as a proper system utility when I want to.
That means python, janet, raku, guile scheme, and so on.
Jank maintainer doesn't want to target system scripting, so jank is no good for me personally.
System scripting entails system libraries.
Ya, that's fair. I think in bb you can bundle your whole script either as an uberscript or an uberjar (but like a bb one). So it would be bundled with it's dependencies. But that does mean if two script use the same lib/version it's duplicated in each, unlike with proper linux package management where they'd point to the same one.
babashka is a project scripting language.
Whenever I used Python, I always used pip and conda and such, and never my distro package manager. But I know my distro does include python utilities that pull down python libs, so at least it has that option.
I don't want multiple package managers. I manage everything with linux distribution package managers.
To me, basilisp is the only hope for clojure as a system scripting language.
Question, in a linux package manager, how does the code knows where to look for dependencies? Like how does python know that libx is in /opt/lib/... (hypothethical)
• Environment variables • Default directories that are determined during build.
Hum...
The default system module directories are determined as compilation options.
You can add more with environment variables.
@amano.kenji I am guessing, (GNU) Guile could be closer than alternatives to your requirement. It is not Clojure, but it's a Lisp.
I might learn guile if I want to use gnu guix system, but learning guile will take some time which I don't want to invest unless I really want or need guile and guix system for some reasons.
For now, I just write in janet language which is simple and which I already know.
I think maybe then you could package bb libs in the system package manager. It would be annoying since no one does so. But you could do:
#! /usr/bin/env bb
(require '[babashka.deps :as deps])
(deps/add-deps '{:deps {coldnew/left-pad {:local/root (str (System/getenv "MY_LIBS") "/left-pad"}}})
(require '[coldnew.left-pad :refer [leftpad]])
(println (leftpad "Hello, world!" 20 "-"))Hum... I think the issue, is that if a lib you depend on depends on other libs, they won't be doing this trick, and so they'd have their libs pull down to your machine by babashka itself, bypassing your package manager again
Hence, basilisp as the only hope for any chance of using clojure as a system scripting language. If basilisp integrates with python module system, it will be a system scripting language. Right now, the integration doesn't exist.
ClojureScript can use node_modules deps no? I guess that's also an option, but ya, it's a lot of hassle to setup just for scripts.
node_modules is a nightmare for most linux distributions.
What I realize again is that nothing is perfect, and I should accept mediocrity. I should accept thing as they are now. I accept that clojure is not a system scripting language, yet. I shall stick to janet for the foreseeable future.
Was just reading a bit on why node_modules is more annoying than venv. Seems because it duplicates everything and maybe pulls in binaries not always built for the local machine?
> I accept that clojure is not a system scripting language, yet. Ya, admittedly, it's not friendly to being included in a distro and used for distro-level packaging. Is Basilisp thinking on using pip or maven/git ?
Doc says:
> PYTHONPATH, sys.path, and Finding Basilisp Namespaces
> Basilisp uses the PYTHONPATH environment variable and sys.path to determine where to look for Basilisp code when requiring namespaces. This is roughly analogous to the Java classpath in Clojure. These values may be set manually, but are more often configured by some project management tool such as Poetry or defined in your Python virtualenv. These values may also be set via https://basilisp.readthedocs.io/en/latest/cli.html#cli arguments.
Wouldn't that make it viable for system scripting?
Ah I see, it doesn't yet have dependency management tooling so it's a question mark
Tight integration with python module system will solve that issue. I know how it should be done, but I don't have time to do it myself. I'd rather pay the maintainer to work on it full-time if I have money. Money buys other people's time. Become rich.
@didibus AFAIK: Raku is Perl6, Perl is Perl5 - https://www.infoworld.com/article/4053221/perl-programming-language-rises-again-tiobe.html
Isn't raku perl?
Also, I'm super down for Basilisp, I love Clojure having many viable runtimes like that, but I'm not sure TIOBE index is a good representation. I mean scratch is number 16 and it's literally a programming language for babies.