Why does BlueStyle do this?

I recently read through BlueStyle (and YASGuide). There are a few items that feel strange to me. I’ll probably conform and get used to it, but I could use some more convincing on why my way is worse.

  1. Why so vertical?
    Characters per line is limited to 80 or 92, but that is only around 1/3 of my 16:9 monitor’s width.

  2. Is one tab per nest level really easier to read than vertical alignment?
    I write containers so they vertically align to the nearest tab. (Aligning to the exact space would be visually better, but that is too finicky to type.) Did the nested container formatting decision drive the line length decision or vice versa?

  3. Why limit inline comments?
    The other place this character per line limit gets in the way is with inline comments. Within a function, I define sections with comments and use inline comments to clarify what is happening. If my inline comments need to move above the line to meet the line length requirement, then my inline and section comments are going to get jumbled together. Also, if I see a non-header comment within a code block, I will think it is code I commented out for debugging. When respecting BlueStyle, what is the best way to differentiate comment types?

  4. Is that the best place for function definition closing parenthesis?
    It really bugs me in the following code copied from BlueStyle for function definitions that the closing parenthesis is left-aligned but is not the end of the code block. I prefer the “No” version for that reason.

# Yes:
function foobar(
    df::DataFrame,
    id::Symbol,
    variable::Symbol,
)
    # code
end

# No: Indented too much.
function foobar(
        df::DataFrame,
        id::Symbol,
        variable::Symbol,
    )
    # code
end
  1. When to use @assert vs. throw(ArgumentError)?
    I didn’t know about @assert before, but BlueStyle says not to use it. It seems like @assert is made for input checking and thus could clean up that section of code from my last screenshot. Should I really avoid it?

  2. Should blank lines have strict rules?
    I use blank lines to separate sections, so I don’t add them between related control flows, or methods linked to the same docstring. BlueStyle also says I should avoid the blank line after function but I sometimes do to make the section header stand out better.

  3. Should Docstrings show a return variable?
    I generally like this docstring template from BlueStyle except that it indicates to me that Int itself is returned from the function not a variable of type Int (result::Int). I think you should have to list and name your Returns the same way you list and name your Arguments, especially for functions that return multiple values. What if I return two Ints? (The Throws section seems unnecessary too. The user can find out about errors, if they happen, in the thrown string.)

"""
    mysearch(array::MyArray{T}, val::T; verbose=true) where {T} -> Int

Searches the `array` for the `val`. For some reason we don't want to use Julia's
builtin search :)

# Arguments
- `array::MyArray{T}`: the array to search
- `val::T`: the value to search for

# Keywords
- `verbose::Bool=true`: print out progress details

# Returns
- `Int`: the index where `val` is located in the `array`

# Throws
- `NotFoundError`: I guess we could throw an error if `val` isn't found.
"""
function mysearch(array::AbstractArray{T}, val::T) where T
    ...
end

I dislike particularly that vertical style. It means one may easily spend half a screen height with a function declaration that does not special that cannot be read in a singe line or two. The consequence is that if one need to read attentively a function body we are often condemn to permanently scroll the code up-and-down. This far-far more distracting than have a line that, a sin for many, is lengthier than, say, 90 chars.

2 Likes

I don’t follow the Blue Style myself. (It’s not compulsory, is it? :joy:)

For 4., this is a JuliaFormatter default. I personally didn’t like it (Minor gripe about formatting result · Issue #31 · domluna/JuliaFormatter.jl · GitHub).

1 Like

I don’t care for that function indentation. Just 4 lines for everything is enough.

As with all style questions, much of this surely comes down to personal preference. Most of your questions probably don’t have an answer better than “someone else prefers it that way.”

This one though, does have a technical reason:

Assertions are generally assumed to be optional for internal consistency checks and can often be disabled. @assert doesn’t have that capability — not yet — but it might in the future. Thus the warning in its documentation:

https://docs.julialang.org/en/v1/base/base/#Base.@assert

An assert might be disabled at various optimization levels. Assert should therefore only be used as a debugging tool and not used for authentication verification (e.g., verifying passwords), nor should side effects needed for the function to work correctly be used inside of asserts.

4 Likes

I like to have 3 or more files (or different sections of the same file) open side by side.
Most lines are relatively short.

By keeping line lengths down, you can maximize the amount of text that appears on your screen at one time.

Having multiple different regions open at once is convenient, as it makes it fast to check them against each other. “Okay, this calls foo, which does…”
Of course, having lines x:y and y+1:z open next to each other is common, too, and then I see a lot more lines of that file than if I had just a single-but-twice-as-wide panel.

I don’t use blue style, but I am a fan of constraining maximum width.
I also like 2 spaces per tab.

6 Likes

It is unfortunate we don’t have that yet.
I’m quite liberal with my asserts when writing C++, because I know that in release mode worth debugging disabled, they’ll disappear and have zero runtime overhead, even if they’re performing costly consistency checks involving looping over data structures.
Makes debugging easier.

But I don’t want to pay that price in Julia.

Disabled by default, but enabled when testing packages or with a command line flag would be great.

We could of course do that manually.

debug() = false

function foo()
    debug() && check()
    ...
end

And then redefine debug() = true inside runtests.jl. Maybe I should.

Maybe it should default to mirroring whether check-bounds=yes is enforced (which it is eject testing).
Then we avoid the need for any extra invalidations.

3 Likes

These do sound useful. Are there conveniences for them in VSCode?

… a way to make double-clicking on a reference open a new tab instead of moving within the current tab.

… a way to synchronize positioning of two tabs so they scroll together.

you can technically achieve that with @debug ? it only runs if you set debug level above something, otherwise the compiled function ignores the entire @debug block, I think?

Ref RFC: implement proper debug mode for packages (with support for re-precompilation) by KristofferC · Pull Request #37874 · JuliaLang/julia · GitHub

2 Likes

I tend to have a maximum number of tabs already open (3-5, depending on monitor size), so I generally don’t want any more tabs to be opened.

I tend to be looking at two different functions in the same file. I don’t think I really want a sliding window that big; I’d want one of them fixed on a particular thing.

No recompilation; it’s based on runtime checks. @kristoffer.carlsson 's PR is what I want.

I see, but it’s cheaper than @assert since you don’t evaluate the expression

We need either functions, or recompilation. The linked PR does the latter.
With functions, you can place the expression behind a compile-time known true/false, and invalidate it. The compiler can then delete it.
With recompilation, it can happen at macro-expand time to delete the expression. This is how assert works in C and C++.

1 Like

Like all style guides it is personal preference.
This was the style guide developed internally at Invenia, mostly before I started.
It was an internal document for years before we open sourced it.

I can shed some light on some bits of it.
Note I don’t agree with all of it as the best choice, but generally I value consistency more.

In general it is designed for:

  • a large code-base, Invenias code base is over 1M lines of code,
  • continously developed for decades (hopefully), outlasting any single employee
  • developed by people of widely varying backgrounds, skills and passions (Invenia has all kinds of folks from senior developers programming for decades, to CS interns, to power systems engineers who might barely have coded before starting but have a whole PhD etc etc)
  • running in a production systems, where crashing out unintentionally can cost serious money and cause serious indirect harms.

Now do i think it accomplishes these goals well? Yes, it’s decent at them.
But so would almost any style-guide consistently used.
This kinda thing is the reason why companies develop most style-guides, not individual working on hobby projects.

A thing in particular to remember is that it is old, as i mentioned.
It’s not showing it’s age and is still applicable to day, but some of the motivations are a little different.
This has a number of effects.

  • Some bits of it are were written when the language worked different. (there used to be parser ambiguities if you didn’t always use semicolons for semperating keyword arguments. Which made a stronger case for always using them.)
  • It is much older than automatic code formatting in Julia. This is a huge one. It is designed with manual inspection in mind, and with things that are easy to tell people how to do. Not things that are easy to do programatically. It leaves a lot of things open as value judgements, and is generally written more as a guideline than a rule..
  • It’s older than 4K Monitors being affordable

As i said I wasn’t around for most of it being written.
@omus can give more history probably, or @iamed2 or @Rory-Finnegan .
But here is what i understand to be true:

Matching the CONTRIBUTING.md of JuliaLang/julia
Though there have been a lot of discussion about relaxing that.

  1. Is one tab per nest level really easier to read than vertical alignment?

It’s easier to roll out as practices consistently across contributors. Especially new ones (who might be interns etc with a lot to learn).
It is a simpler operation to describe.
Alignment is harder to describe.

Personally, I would do what BlueStyle does here, but with tabs.
This is the style that looks consistent no matter what you set your tabsize to.
(the other one is indent with tabs, align with spaces, but even more complex to describe)
But I don’t personally decide things.

  1. Why limit inline comments?

See 1. but yes there have been even more discussion about removing the limit for comments, (also for string literals)

  1. Is that the best place for function definition closing parenthesis?

Again, it is simplest to decribe.
Indent once whenever you need to break stuff over multiple lines.
(Not indent twice for arges and once for closing brace if it is a function definition args, and once elsewhere)

  1. When to use @assert vs. throw(ArgumentError)?

@assert is a common thing across languages.
It is the semantically a comment saying the following is always true, or else the library author screwed up. It happens to be a comment that is automatically verified when it is passed over.

For input argument checking it is not always true – that can happen because the library user screwed up.
In some versions of julia @assert might be silently ignored.
this means if it does happen your program can enter a undefined state.
(which can be really bad. Like old languages which just ignored errors and executed the next line).

  1. Should blank lines have strict rules?

I have never seen this strictly followed except when line breaks get really excessive.

Note the BlueStyle introduction

When adhering to this style it’s important to realize that these are guidelines and not rules.

  1. Should Docstrings show a return variable?

The docstring part of style guide I feel like is mostly ignored.
It’s mostly over engineered. And too verbose.

5 Likes