Open-sourcing the Blue Style Guide for Julia

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.

4 Likes

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.

By the way, while I don’t know where the 92 line length originates from, Python’s Black format (which uses 88) points to this video recommending “90 ish”.

(Possibly the precedent for shorter line lengths comes from punch cards.)

1 Like

cf

2 Likes

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.

  1. 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 = "")
    
  2. making trailing commas mandatory various expansions (after a while, the code settles and then they are not needed)

  3. insisting on no space around = in keyword arglists/named tuples; it provides a nice visual separator

  4. not using DocStringExtensions.jl (this produces a lot of repetition in docstrings that will go out of date)

  5. insisting on return for the last value (tends to be left out in a “lispy” style)

(This is not intended as criticism, just a list of differences.)

1 Like

Actually we do use it in LibPQ.jl, and it is indeed very nice. It’s certainly compatible with the Blue style :slight_smile:

1 Like

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.

2 Likes

You can do using Bar: Bar.

5 Likes

Also, 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)

4 Likes

I find 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 import.

4 Likes

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 using/import is to always using and the exact additions to the namespace. I only qualify when there is ambiguity (CSV.read / 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.

Example,

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.

4 Likes

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 Base.

1 Like

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).

1 Like

You seem poised to cut a large swath :wink: I was talking more about files I can actually modify (i. e. that I own).

1 Like

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.

1 Like