Sample output from running Runic with check and diff mode enabled on a small code base.
Just like e.g. gofmt (Go formatter) and black (Python formatter), Runic has no configuration. This is a feature that, in particular in the Go community, is very popular which is why I wanted to try this out also for Julia.
I have tested the formatter on all julia code in base and all julia code in my .julia folder without errors (although I have not visually inspected all those tens of thousands lines of code, of course) so I would consider it safe to start trying out.
Note that the package is not yet registered so ideas for more transformations or feedback on current transformations are very much appreciated! Please open an issue if you have any type of feedback.
Also, I very much agree, that the absent configurability can be a huge advantage.
Of course formatting style is a matter of taste and the applied style does not need to (and certainly will not) be everyones favourite.
Also, I find all style choices at least reasonable.
Or rather all except one…
I feel like spaces around keyword arguments really decrese legibility.
Also, I have never really seen this style in the wild and thus would be interested in why you made this decision?
Do you prefer it like that? Do others prefer it like that? Or is it for the sake of parser simplicity and/or pragmatism, handling all =s in the same way?
If this is due to the latter, would you consider changing this - if an overwhelming majority of users have similar reservations?
However, irrespective of my critisism, I could see myself employing Runic.jl as default formatter.
EDIT: After some healthy rumination, it became clear to me that my primary objection to space-flanked kwarg-= is in function definitions/calls formatted as a single line.
As soon as the formatting is one line per kwarg I might still not find it appealing, but in terms of code legibility I think it’s perfectly fine.
This is actually the original reason. This comes as a result of me implementing the “spaces around assignments” rule without restricting the context of it and realize afterwards it also happened to apply to keyword arguments in function definitions and function calls.
It looks like at least SciMLStyle recommends it (although it is a bit cryptic).
I realized after trying Runic on some code bases that I was very inconsistent. For example I think I (used to) prefer no spaces around “simple” keyword arguments like e.g. foo(; bar=true) but not more complicated ones like e.g. foo(; bar = baz(foo + 3)) etc. I think that now I have come around to always use spaces because of consistency and because it seems kind of arbitrary to not use spaces in this specific context but everywhere else.
Since you’ve mentioned “context”, I wanted to ask: Are there contextual transformations that would be in scope for this project?
I’ve never been a fan of Julia being team TMTOWTDI, and I wonder how practical it’d be to have a tool that does more complex normalizations. For example, transforming
I also usually prefer no spaces, but one argument for spaces is that they prevent syntax ambiguity errors. Specifically, if the keyword ends in a bang, like foo! = bar, the no-space version is parsed as foo != bar, which is obviously not valid keyword argument syntax.
Well, obviously, a package like this invites lots of comments because everyone’s preferences are different. But I can’t help myself
- function test(a::Union{Int,Float64}, b::T; foo=:bar) where {T<:Number}
- # body
- end
+ function test(a::Union{Int, Float64}, b::T; foo = :bar) where {T <: Number}
+ # body
+ end
Personally, in addition to kw args not having spaces I really like not having spaces within { } blocks, which is also part of some existing style guides.
For me, there are two reasons for that:
Function signatures can be relatively complex without going crazy in length.
Spaces are about separating different “semantic” blocks in an expression. I think function signatures should highlight the separation of arguments first. Type information and keywords are tightly bound to the respective argument
I think { } blocks, in general, tend to be messy, especially when nested. Let’s embrace the chaos and omit spaces, they’re ugly either way!
But of course everybody taste is different, which is why I quite like the approach of not having any options and would consider using your package, regardless of the rules you settle on.
Big up for the readme, it’s super nice that you included tutorials for git hooks and GitHub actions!
Obviously it’s all very subjective but I think the fact that these signatures can be complex and {} blocks can be too is, in my opinion, even more reason to want spaces.
And Runic is really nice, thanks for this great contribution @fredrikekre! I’m really pleased with how it looks in my code.
Thank you for this package, this is very close to my preferred style so I am considering using it going forward. Small issues I noticed trying it out:
In some situations involving macros, Runic seems to change meaning. For example, in
using Catalyst
net = @network_component net begin
r, X --> Y
end
net′ = extend(net, @network_component (@species Z(t);))
Runic removes the ;, making the syntax invalid.
Another issue is related to linebreaks after =, both for short-form function definitions and variable assignments. Runic changes
y =
if x
1
else
2
end
to
y =
if x
1
else
2
end
which is probably not intended.
(The only style choice I disagree with is that
function f(
x
)
x
end
is formatted as
function f(
x
)
x
end
because I feel that the ) should be indented just like the start of the line containing the matching ( and the double indentation is unnecessary in my opinion. Ideally I would also prefer
f([
a
b
])
to be allowed, that is, Runic not forcing a linebreak before [ in situations like this, but I could get used to it.)
This is also such an arbitrary rule though. Why spaces in lists surrounded by () and [] but not {}?
Can you open an issue about this?
And this too?
The reason I dislike your preferred style there is that arguments and the function body have the same indent which can make it a bit difficult to separate them at times. And the closing ) as the first characther look like it closes the function like a closing } in e.g. C. I like that everything between function and end are indented for this reason.
While this was already my preferred style it isn’t actually directly encoded in Runic but just follows naturally from the two indenting rules saying that i) everything between function and end have an increased indent level of 1, and ii) argument list to multiline function calls (f(\nx\n)) result in another increase of indent level.
In general Runic style isn’t “my” style so I have also had to accept certain formatting results as the come. Instead Runic implement a set of rules and then it applies them ruthlessly everywhere.
I don’t know much about Go, but I am not sure that this is an ideal fit for a language like Julia, which has a very rich syntax. There may not be a single ideal formatting, even for one person, as it depends on the context (how much one wants to save vertical space, etc).
Given all the effort you have invested into writing this package, I wonder how much extra it would take to parse various options from a TOML file, put them in a struct, and use that for emitting output. Cf #12, #34 (semicolons could be useful in code meant for interactive use), and various tiny issues you may encounter in the future for rare corner cases. Instead of having to think about a “right” choice, you can leave it open, and just have a default for the choice you consider most sensible.
The point is that there is no ideal formatting, but that global consistency improves readability in new code bases! Any particular decision has tradeoffs in different contexts, but those tradeoffs are far out weighed just making a call and sticking with it.
I roughly agree that consistency in style is good, but IMO style is an aesthetic issue that would ideally be left to the programmer to decide upon on a case-by-case basis, just as programming itself is done.
To a small extent, maybe. That said, I doubt that the very minor choices have a huge impact on readability. Eg I can read both
a = 1
ab = 2
and
a = 1
ab = 2
just fine. Personally I use the first, but if I am contributing to a codebase that uses the second, I try to do it like that.
It is my impression that the Julia community is much more laissez-faire than Go with its single best way for everything. The Julian approach, IMO, is to go for Swiss army knives that include a chainsaw.
I imagine the use case is less for formatting your own code and more for formatting pull requests and collaborative code bases into consistency: an automatically enforced style guide.
Then you can write your code however you are used to and let the formatter adjust it later.
No doubt. But I still don’t like the idea of my formatting being changed. Luckily for me most of my coding is on individual projects, so I can follow my own style anyways.