Best practice for avoiding line break bugs in long expressions


#1

I had a very frustrating debugging session yesterday. My code was running without errors but producing incorrect results. It took at least two hours for me to find this problem:

x = a + really * long +
    + (and + complex) / expression +
    - that * (spans - several)
    + lines * of / code

Can you spot the error? The problem is that the expression terminates after the third line because I forgot to hang a trailing + sign there. The fourth line evaluates without error but doesn’t do anything.

If you’re wondering why I use the convention of prefixing new lines with the operator, the reason is readability. In my opinion it is much more easy for the eyes to parse this:

10
+ 3
- 4
+ 5

… than this:

10 +
 3 -
 4 +
 5

Sadly, Julia’s line break rules encourage the latter style. As a workaround, I usually use the prefix style but add dummy + signs at the end of each line when the expression needs to continue.

My questions for the forum:

  1. Is there a better convention for long expressions which is just as readable as my prefix style but avoids silent line break bugs? Ideally, a missed operator should produce a syntax error.
  2. Would it be possible to have Julia produce a warning for “hanging expressions”, i.e. expressions that do not store results in a variable or return a value from a function?
  3. Out of curiosity, why are hanging expressions allowed in the first place? Is there a use case?

#2

Your editor should indent correctly, and that can serve as a warning.

That said, the best practice is to avoid expressions spanning multiple lines if possible. Group calculations, and make your code more readable. The compiler won’t care, it’s job is to put it together.


#3

Is there a better convention for long expressions which is just as readable as my prefix style but avoids silent line break bugs? Ideally, a missed operator should produce a syntax error.

Yep:

x = (a + really * long     
     + (and + complex) / expression
     - that * (spans - several)
     + lines * of / code)

Would it be possible to have Julia produce a warning for “hanging expressions”, i.e. expressions that do not store results in a variable or return a value from a function?

I don’t think so. Operators aren’t really different from functions, and functions may have side effects. So when you write:

a + b

depending on operand types, + may be a function, for example, printing its arguments to the screen or adding b in-place to a, or whatever else.


#4

I just tried Sublime Text and VScode with Julia syntax. Neither of them auto-indented a multiline expression, nor de-indented a manually indented expression when a line ended with something other than an operator. Do you know of an editor that helps with this?


#5

Good point. But I wonder if anyone has a practical use case for operators with side effects? After all, Julia has a very strong convention for using exclamation!() to warn when side effects happen in ordinary functions, so I imagine that they are quite rare in operators.


#6

exclamation!() is just a convention, not a compiler requirement. Also, it’s mostly used for functions mutating state of its arguments, but there are other side effects, e.g. almost all IO functions such as println(x), write(io, x) or imshow(im) clearly make side effects, but don’t use exclamation.

If you want a realistic example of operators with side effects, consider C++ style IO:

<<(io, x) = println(io, x)
io = IOBuffer()
io << "hello"

By the way, another pretty solution for long expressions is to split them into several like this:

x = a + really * long
x += (and + complex) / expression
x -= that * (spans - several)
x += lines * of / code

#7

I am using Emacs, with julia-mode, and if I press RET after the +, the next line starts indented properly. I am pretty surprised that other editors don’t do this.


#8

I always wrap multiline expressions in parentheses to avoid errors like the one you mentioned. I developed this habit after reading PEP8, which says

The preferred way of wrapping long lines is by using Python’s implied line continuation inside parentheses, brackets and braces. Long lines can be broken over multiple lines by wrapping expressions in parentheses. These should be used in preference to using a backslash for line continuation.

This is also a common recommendation in Javascript style guides, as Javascript has “automatic semicolon insertion” which some people find unintuitive.


#9

Experiments suggest that the compiler does not (yet?) optimize this sort of construct very well for nontrivial x. This differs from, e.g., C++ and Fortran compilers which are designed to spend more effort on optimzation. So I beg to differ with @Tamas_Papp: the compiler does want help here.

So I vote for lots of parentheses. (Presumably that’s what Tamas means by grouped calculations; I’d be shocked if a Lisp aficionado like himself would suggest otherwise.)


#10

At this point, my priority is to make my code readable, and wait for the compiler to catch up. Recently I have had multiple occasions of staring at v0.4 code that I mangled to make it a bit faster in performance-critical parts, rendering it very difficult to read. So nowadays I just write my code as I would like, and optimize the occasional critical part.

Compared to the Lisp family, Julia favors breaking up to subexpressions because assignments to new variables do not indent. Compare

(let ((x something))
  (if (is-something? x)
      (let ((y (calculate-from x)))
        ...)))

with

x = something
if is_something(x)
    y = calculate_from(x)
    ...
end

so I find that my code is “flatter”.


#11

Thank you everyone, there are several useful suggestions in this thread. I’ll probably wrap my long expressions in parentheses from now on. Special thanks to @dfdx for disarming my gotcha questions with great examples. :slight_smile: