Runic.jl: A code formatter with rules set in stone

Because it saves time you’d otherwise spend on discussing and implementing formatting preferences. If one package is good enough for most people, then at least most packages will be formatted reasonably and this aligns people’s expectations of what Julia code looks like. I think that’s preferable to having many different opinions floating around on what are essentially pretty arbitrary decisions anyway.

This will only work anyway if the defaults chosen are good enough, as otherwise people won’t want to use it. But if they are, and usage is simple, I think this package can succeed. I’d use it.

7 Likes

But that is clearly not the case: even if you pick one set of rules, there will be discussions (like this one), or see the open issues for Runic.jl.

The idea that picking a set of defaults and moving on (about anything, including issues much broader than code formatting) will quench discussion forever so we can Get Work Done :tm: is an illusion. Reasonable people will continue to disagree or just ask questions from time to time.

4 Likes

Well you can see it in other ecosystems that it can work. It doesn’t have to, but it can. This discussion here is just one, but it potentially replaces many others being held in individual repos down the line.

1 Like

But the alternative also just exists, if you do not Agree with Runic.jl, you can just continue to use JuliaFormatter and config it to all your personal preferences.

I think having both these is a very good equilibrium. I am currently using JuliaFormatter, but thinking a bit less about how it is configures is something that I feel is very nice.
Also the choices I for now see in Runic.jl are better than what my current own config (slightly modified JuliaFormatter defaults) does.

And I agree, if we can get more people to use just one (sure opinionated, set by just a few people, currently maybe just Fredrik) – is a point for discussion. Still, for me a really great initiative!

6 Likes

I’ll be interested to try applying Runic to some code I’ve written and see what I think of the changes it makes

1 Like

I should perhaps note that Runic isn’t really strict with what syntax you use (e.g. short form vs long form function definitions etc). For example, adding two variables in Julia can be done with +(a, b) or a + b. Runic doesn’t care what you pick, but if you pick the function style it will be formatted as +(a, b) and if you pick the infix version it will be formatted like a + b.

The main thing with formatters (and in particular no-config formatters perhaps) is that it completely eliminates this discussion (in e.g. code review) and you don’t have to think about formatting when you write the code. Perhaps it doesn’t matter much for single-person projects, but when you start to collaborate on something it is nice that the style is consistent and not depending on who authored the function.

I also think it simplifies code review. I see quite often that contributors make formatting changes elsewhere in the code base which will then result in another review cycle when you have to ask the contributor to remove unrelated changes. If you enforce a style in CI then if CI pass you don’t need to or should not comment on the style.

I don’t know. Is there a way to configure something like formatprg in VSCoder perhaps? Or some extension that let’s you configure external formatters like conform.nvim perhaps? It would be nice not to have to write a full extension at least. I can ask in the #vscode channel on Slack.

Right, the idea isn’t to come up with rules that everyone approves of. The idea is to come up with something that people can accept so that configuration is not needed.

Right, I guess Go is the prime example. All Go code look the same because all Go code is formatted with gofmt because it was available already early on and everyone bought into the idea. See for example https://www.youtube.com/watch?v=PAAkCSZUG1c&t=523s :

Gofmt’s style is no one’s favorite, yet gofmt is is everyone’s favorite.

8 Likes

Is there a reason that runic couldn’t just be a style for JuliaFormatter.jl? One could opt in via a .JuliaFormatter.toml file with content

style = "runic"

and then have a hard rule that this style doesn’t accept any further options?

I think that would in general make the tooling story less fragmented.

22 Likes

There could be some selection bias involved in that. Those who didn’t buy into the idea might simply have chosen to move on to other languages.

1 Like

At least for my part, I would be happy to use a set of defaults and not think too hard about it if I find the defaults reasonable — even if they’re not a perfect match to what I would have done tabula rasa

*

modulo a few exceptions in formatting choices I find particularly offputting

The other three languages I use are Python (format with black), Go (format with gofmt) and C++ (anarchy :smiling_face_with_tear:)

to be honest it seems unlikely to me that more than a very small number of users would pass up Go solely because they didn’t like some stylistic choices of gofmt.

1 Like

This is super neat, thank you @fredrikekre for your work on this!

I would like to lend some support to @Tamas_Papp’s suggestion. From a skim of this thread, it seems like I am in a boat that many people are in: all of these defaults you have seem perfectly reasonable, but it’s hard to imagine using this tool myself because I have some of my own weird preferences (for example: I like two spaces instead of four for each indentation).

The biggest appeal of something like this for me is that, should every contributor use it, it will reduce the noise in git diffs and stuff like that. And having a very clean git history is always great. If I were to work on projects that had a significant number of contributors, I would happily adopt this no-configuration formatter, because in some sense it seems fair to pick a standard that doesn’t fit anybody’s exact preferences. But most of my code projects are just me and a small number of collaborators/students/whatever, and for smaller projects like that where maybe 2-3 people could actually reach some consensus on personal and per-project preferences, the sacrifice feels less necessary.

6 Likes

@fredrikekre this is awesome! I have been wanting an opinionated formatter, like black for python, for Julia for a long time. In my company whenever we write python it’s black formatted and whenever we use go it’s of course gofmt. I’ll be changing my neovim config tonight. :blush::pray:t2:

5 Likes

Somehow I couldn’t let this thought go and finally I think I got to the bottom of it. Consider

foo(; kw1="bla", kw2="blu", kw2="blub")

vs

foo(; kw1 = "bla", kw2 = "blu", kw2 = "blub")

Written in the second style, the value of a kwarg is actually closer to the key of the following kwarg than to its own (separated by 2 characters vs 3).
Of course this purely visual first impression matters only if you merely glance over it quickly - but still, I feel this is significant.

Just my 2c.

10 Likes

@pfitzseb found the extension Custom Local Formatters which is similar to conform.nvim. I added instructions for using Runic in VS Code with this extension in the README, see Editor integration – VS Code.

(A more sustainable option long term would probably be to hook up Runic with the language server formatting but since I am not using VS Code I am not likely to spend time on that in the near future. Perhaps someone wants to contribute it and in that case feel free to reach out.)

4 Likes

I have nothing against Runic.jl, just don’t understand the motivation (but note that I find it totally OK to just write packages without explaining why, no one should have to explain that, I was merely curious).

One can ignore JuliaFormatter’s configuration altogether, ie implicitly just use JuliaFormatter.DefaultStyle.

Given that there are 3 major styles already in JuliaFormatter.jl (YAS, Blue, SciML), I am skeptical about universal acceptance of any new proposal. IMO the most likely outcome is


That said, I think this is fine.

8 Likes

As for package announcements, I would prefer if they followed the motto: always explain, always complain.

2 Likes

Having just played around with it, I am quite happy with it. It feels snappy and your readme is exemplary. Ran it over KernelAbstractions and Runic and I seem to mostly agree in style. So big kudos.

18 Likes

But I like using ∈ in for loops! :cry:

6 Likes

This is amazing, thank you so much!

1 Like

Runic can now be (statically) compiled with the juliac compiler driver from JuliaLang/julia#55047 :tada: :

  1. Compile the branch from JuliaLang/julia#55047 alternatively get the preview build with juliaup add pr55047 and juliaup default pr55047
  2. Clone the Runic repository
  3. Run make in the juliac subdirectory which compiles the runicc binary
    $ ls -lah runicc
    -rwxrwxr-x 1 fredrik fredrik 2,9M sep  5 14:17 runicc
    

Compiled execution:

$ time ./runicc <../src/Runic.jl >/dev/null

real    0m0,078s
user    0m0,074s
sys     0m0,016s

Regular (precompiled) execution (e.g. julia -e 'using Runic; Runic.main()'):

$ time runic <../src/Runic.jl >/dev/null

real    0m0,328s
user    0m0,314s
sys     0m0,084s

Runic is surprisingly fast even without compiling, but if you use e.g. “format on save” or similar you can sometimes notice a tiny lag for larger files. With the compiled binary you don’t notice it at all.

31 Likes

On 150ced92abc487b3ce47a0ce5d80aec501b6baab I get

/home/mose/.julia/juliaup/julia-pr55047/bin/julia --project=. /home/mose/.julia/juliaup/julia-pr55047/share/julia/juliac.jl --output-exe runicc --trim=unsafe-warn runicc.jl
Dynamic call to Base.ErrorException(Any)
In deprecated.jl:261

Stacktrace:
  [1] _depwarn(msg::Any, funcsym::Any, force::Bool)
    @ Base deprecated.jl:261
  [2] #invokelatest#1210;
    @ ~/.julia/juliaup/julia-pr55047/share/julia/juliac-buildscript.jl:39 [inlined]
  [3] invokelatest;
    @ ~/.julia/juliaup/julia-pr55047/share/julia/juliac-buildscript.jl:38 [inlined]
  [4] #depwarn#1202;
    @ deprecated.jl:255 [inlined]
  [5] depwarn;
    @ deprecated.jl:250 [inlined]
  [6] parse!(stream::ParseStream; rule::Symbol)
    @ JuliaSyntax ~/.julia/packages/JuliaSyntax/BHOG8/src/parser_api.jl:44
  [7] parse!;
    @ ~/.julia/packages/JuliaSyntax/BHOG8/src/parser_api.jl:42 [inlined]
  [8] _parse(rule::Symbol, need_eof::Bool, ::Type{GreenNode{Head} where Head}, text::String, index::Int64; version::VersionNumber, ignore_trivia::Bool, filename::Nothing, first_line::Int64, ignore_errors::Bool, ignore_warnings::Bool, kws::Pairs{Symbol, Union{}, Tuple, NamedTuple{?, Tuple}})
    @ JuliaSyntax ~/.julia/packages/JuliaSyntax/BHOG8/src/parser_api.jl:84
  [9] _parse; (repeats 2 times)
    @ ~/.julia/packages/JuliaSyntax/BHOG8/src/parser_api.jl:77 [inlined]
 [10] parseall;
    @ ~/.julia/packages/JuliaSyntax/BHOG8/src/parser_api.jl:143 [inlined]
 [11] Context(src_str::String; assert::Bool, debug::Bool, verbose::Bool, diff::Bool, check::Bool, quiet::Bool, filemode::Bool)
    @ Runic /tmp/Runic.jl/src/Runic.jl:155
 [12] Context;
    @ /tmp/Runic.jl/src/Runic.jl:150 [inlined]
 [13] main(argv::Array{String, 1})
    @ Runic /tmp/Runic.jl/src/main.jl:342
 [14] main(argc::Int32, argv::Ptr{Ptr{UInt8}})
    @ RunicC /tmp/Runic.jl/juliac/runicc.jl:16

and several other similar errors

julia> versioninfo()
Julia Version 1.12.0-DEV.1145
Commit d596feba1d1 (2024-08-28 20:00 UTC)
Build Info:
  Official https://julialang.org/ release
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: 8 × Intel(R) Core(TM) i7-4870HQ CPU @ 2.50GHz
  WORD_SIZE: 64
  LLVM: libLLVM-18.1.7 (ORCJIT, haswell)
Threads: 1 default, 0 interactive, 1 GC (on 8 virtual cores)