Thank you for this! I might adopt it in my packages. The style guide is quite extensive and I’ve only skimmed it, but I haven’t found a single major thing I disagree with so far. Everything seems to make sense; it shows that it has been battle-tested. I like that it’s not just blindly based on another language’s / company’s style guide, and I also like that it leaves some wiggle room.
Hurray! I really hope some people will find this useful. It’s at least helped me get used to a consistent way of writing code without having to think about format, and mostly removed style discussions from PRs.
(Possibly the precedent for shorter line lengths comes from punch cards.)
This is a very detailed style guide. Thanks for making this public.
Most of the recommendations are very sensible, but I see the following done differently in a lot of packages.
long argument lists
# Blue Style function foobar( df::DataFrame, id::Symbol, variable::Symbol, value::AbstractString, prefix::AbstractString="", ) # break then indent (note matching indentation, break before 92 function foobar(df::DataFrame, id::Symbol, variable::Symbol, value::AbstractString, prefix::AbstractString = "")
making trailing commas mandatory various expansions (after a while, the code settles and then they are not needed)
insisting on no space around
=in keyword arglists/named tuples; it provides a nice visual separator
not using DocStringExtensions.jl (this produces a lot of repetition in docstrings that will go out of date)
returnfor the last value (tends to be left out in a “lispy” style)
(This is not intended as criticism, just a list of differences.)
Actually we do use it in LibPQ.jl, and it is indeed very nice. It’s certainly compatible with the Blue style
You may want to import the functions from the module
Foo without bringing them into the scope of
Bar, so that you can make the function
Bar.f without having to take into account what methods of
Foo.f exist — e.g. if you don’t want to extend
Foo.f with new methods, but create your own function with the same name, which might have a completely different purpose.
You can do
using Bar: Bar.
using Foo: f allows one to be lazy and write
f unqualified. On the other hand,
import Foo requires that I qualify all symbols from
Foo, which is what I want when using
Foo in another module.
Don’t write that then… (
using Foo: Foo)
import Foo more clear than
using Foo: Foo. But, if you can really do everything with
using, then the small loss in clarity is worth the simplicity obtained by ditching
Apparently, this is not a minor issue. I just googled: julia difference between … (I was not looking for import and using), but it autocompleted to “using and import”.
The style I use concerning
import is to always
using and the exact additions to the namespace. I only qualify when there is ambiguity (
JSON3.read). I only extend functionality to functions that are brought by
import Pkg: method_I_will_extend. I like the distinction and safe approach that follows.
The thing I don’t like about
import Foo: bar is that then in some other place in the code you see
function bar(x::MyType) ... end and it’s not very explicit that you’re extending some other package’s function rather than defining your own. I prefer to have
function Foo.bar(x::MyType) ... end whenever I’m adding a method to another package’s function.
The counterpoint would be that you can see every single function being extended in one place rather than go hunting across files to figure that out.
Not so. Do a “Find in the project” of for instance
Base.count (when extending
count). It pops up readily.
I think it would be harder to find if
count wasn’t prefixed with
You would have to do so for every function for every module (Base, Core, stdlibs, submdules, transitive deps, etc.). I like being explicit in one place rather than explicit each instance, but everywhere.
Just a suggestion: you could use GitHub releases to set up a versioning scheme for the style guide. That way users can point to a specific version, rather than having a moving target (as the language itself continues to develop as well).
You seem poised to cut a large swath I was talking more about files I can actually modify (i. e. that I own).
Good point - that’s a tradeoff. I think in practice I don’t often need to answer the question of which methods get extended, and generally if I want to know that I have followup questions about which of my types have new methods defined, so I think I end up going to the implementations anyways. But that seems like it’s wading pretty deep into personal workflow territory.