30
$\begingroup$

A lot of C-family programming languages (C, C#, Java, etc.) require explicit line-ending characters in the form of semicolons. However, newer languages (Python, Kotlin) can infer the end of a line from the placement of newlines (although they often optionally allow semicolons as well). What are the advantages and disadvantages of using explicit line-ending characters versus inferring line-ends from newlines?

$\endgroup$
1
  • 3
    $\begingroup$ You mean the end of statements (or similar) not the end of lines I think. Lines can be continued. $\endgroup$ Commented May 30, 2023 at 17:00

9 Answers 9

26
$\begingroup$

No ambiguity with line wrapping expressions

This is especially important in languages where the final expression is the return value. Is the following code:

func foo() -> Int {
  bar()
  - 5
}

expected to call bar and then return -5, or to return bar() - 5? If semicolons are required, the former would be:

func foo() -> Int {
  bar();
  - 5;
}

while the latter would be:

func foo() -> Int {
  bar()
  - 5;
}

Of course, I wouldn’t line wrap for such a short expression, but the point stands.

$\endgroup$
1
  • 2
    $\begingroup$ I find it funny that FORTRAN marked continuations at the start of the following line, even though it would have been slightly cheaper to mark continuations on the previous line (allowing each line to be processed before reading the next line to ensure that it wasn't going to be continued), but other languages don't do that even though it would make continuations easier to see for programmers. $\endgroup$
    – supercat
    Commented Jul 2, 2023 at 16:17
13
$\begingroup$

Disadvantages of using line ending characters

  • More visual noise. Every token less on the screen is one less that takes up valuable space in your brain

Disadvantages of not using line ending characters

  • Chaining operators needs parenthesis. This creates twice as much visual noise as semicolons would have.
let var = (a
           + b
           + c)

Instead of

let var = a
          + b
          + c;
  • Chaining functions needs the enclosing parenthesis on the new line:
a.b(
).c(
).d()

Instead of

a.b()
 .c()
 .d()
  • Dynamic insertion creates ambiguity (This one is Javascript specific, the rest should work in most languages)

In Javascript

return
"b"

is actually

return;
"b"

thus this function will return undefined. This can be considered confusing behavior to those not used to it. Explicit line endings do not have this issue.

$\endgroup$
9
  • $\begingroup$ I think you just ninja’d me and I didn’t even notice until I clicked “post”. $\endgroup$
    – Bbrk24
    Commented May 16, 2023 at 17:10
  • $\begingroup$ @Bbrk24 our answers are quite different though, they both have value $\endgroup$
    – mousetail
    Commented May 16, 2023 at 17:11
  • 1
    $\begingroup$ JavaScript is a weird language. Semicolons are almost always optional. The only time I've ever used one in my code is the very rare instance where a line began with "(" and I had to enter it as ;(. $\endgroup$ Commented Jul 8, 2023 at 13:27
  • 1
    $\begingroup$ All the disadvantages listed here for not using semicolons are not applicable to F#. These may just be JavaScript quirks $\endgroup$
    – James
    Commented Jul 11, 2023 at 14:31
  • 1
    $\begingroup$ I'd advise against JavaScript's approach. Either consistently require newlines between statements (with some sort of continuation syntax for multi-line statements), or require semicolons between statements. Allowing either and trying to guess what the programmer meant will cause trouble. $\endgroup$
    – dan04
    Commented Jul 11, 2023 at 16:03
9
$\begingroup$

Statement separators (as opposed to terminators in C-style languages) are also common in functional languages when you want to run code for side effects.

Take OCaml, for instance. It's very common to have your function body be a single expression, so you don't see the semicolon very often at all. If you need an if statement, that's still just one expression that happens to have inner parts.

let rec factorial n =
    (* Look ma, no semicolons! *)
    if n = 0 then 1
    else n * factorial (n-1)

Likewise, local variables are introduced with let ... in, not with a separate variable declaration statement like in, for example, Javascript.

let add_to n =
  let value = some_complicated_expression in
  n + value

The only time you explicitly need the semicolon is if you want to run an expression for side effects (discarding its values), then return something else. Haskell has a function called traceId that prints the given string and then returns it. We can write this in OCaml as follows.

let trace_id x =
    print_endline x;
    x

We run print_endline, not because we care about its value, but because we want its side effects. Then we return something unrelated.

In a language like this in which sequencing multiple expressions is the exception rather than the norm, an argument could be made that the added complexity of auto-detected line endings is outweighed by the rarity of their use. So in a functional programming language, simplicity of design is a huge benefit to explicit expression separators.

$\endgroup$
1
  • $\begingroup$ Whereas the actual Haskell implementation of traceId works out to traceId a = trace a a where trace string expr = unsafePerformIO $ do { traceIO string; return expr }; traceIO msg = withCString "%s\n" $ \cfmt -> do { let (nulls,msg') = partition (=='\0') msg; withCString msg' $ \cmsg -> debugBelch cfmt cmsg; when (not (null nulls)) $ withCString "WARNING: previous trace message had null bytes" $ \cmsg -> debugBelch cfmt cmsg }. debugBelch is a foreign import, presumably implemented in C. $\endgroup$ Commented Jul 20, 2023 at 20:07
4
$\begingroup$

One pro is that explicit statement break syntax allows the programmer to avoid this tradeoff:

  • I need to do extra work to allow a long statement to span multiple lines
  • It is not obvious whether a statement ends at the end of this line when I can't see the next line or when the rules are complex.

And these cons can be mitigated with IDE plugin support:

  • semicolons can be visually noisy especially when statements are short,
    but an IDE can style them in low-contrast to de-emphasize them.
  • semicolons require extra typing and conflict with muscle memory from languages like JavaScript where they're optional,
    but can be auto-inserted by an IDE

For the first, consider

let myString = "Here's a line of text"
             + " and another line."

Maybe, since + can be a prefix and infix operator, you say that if the first token of the next line can be an infix operator, it continues.

But that causes problem when the program text seems to have a logical break.

let x = f()

// A really long explanatory comment that
// is essential to understand but which also
// ensures that the last and next non-comment
// lines will never be on the screen at the
// same time.

(complexExpression).something()

You can solve the first problem by continuing expressions by default. But that risks unintended merging in the second example. The author probably did not expect f() to merge with (complexExpression).something() to parse like (f()(complexExpression)).something(), but that's a problem in Javascript because parentheses are not just for grouping; in infix position they're function application.

Maybe you can solve the logically breaking comment problem if your statement breaking decisions are sensitive to whitespace or comments, but probably at the risk of other potential surprises.

Explicit statement breaks lead to simple, easily explained rules as to where one statement ends and the next begins.

$\endgroup$
3
  • 2
    $\begingroup$ "...at the risk of other potential surprises." Such as the parsing behavior changing when a comment is deleted, for instance. $\endgroup$ Commented Jul 20, 2023 at 20:20
  • $\begingroup$ I wonder whether it would be useful as an alternative to have a start-of-statement character? This would make line conditions much more visually obvious, since a continuous vertical line of the same character would be have a gap in it, at the same time as it reduced visual noise since the characters for a section of code sharing the same level of indent would all aligned in a vertical stack. $\endgroup$
    – supercat
    Commented Oct 19, 2023 at 16:24
  • $\begingroup$ @supercat Analogously in natural language, Discourse markers are "include the particles oh, well, now, then, you know, and I mean". $\endgroup$ Commented Oct 25, 2023 at 20:06
3
$\begingroup$

In my view, there aren't any extant advantages for using compulsory statement terminators (the blackletter kind of terminator, I mean, not whitespace like CRLF).

Many languages show that statement terminators are not necessary.

The main advantage of optional statement terminators is to allow more than one statement per line - not, as some others suggest, to ease the formatting of a single statement over multiple lines.

In some old languages like C, the syntax was also carefully designed in some respects to avoid verbosity and extravagance over the vertical axis of the screen. C was also designed to be highly economical with computer memory during its compilation. Those constraints no longer apply with anything like the intensity they applied in the 70s.

A language of modern design would almost always regard a statement as terminated by a newline alone, unless the content of the next line begins at an indentation in excess of the base indentation of that block.

$\endgroup$
0
3
$\begingroup$

Languages that use the end of line as the sentence separator need a special marker to use when the long sentence continues to the next line. Fortran uses continuation character at the position 6 (can be any, there are coding rules which one is more appropriate). Bash and Python uses reverse slash, \.

A language may have special rules when the sentence continues to the next line without the marker, like in Python you can break within parenthesis.

The benefit of using new line as the statement boundary and then a continuation character if the statement spans over multiple lines may be that most of the sentences are short so continuation character is rare. Differently, if another symbol is used to separate the sentences, it must occur between any two sentences anyway.

The drawback of the new line marker that is placed at the end of the line is that if just spaces follow this marker, it suddenly "does not work" without obvious clue why. At least in Bash, a special attention must be paid there are no spaces after the \.

$\endgroup$
2
$\begingroup$

Disadvantages of requiring semicolons

  • It's common to want to edit text from some point up to the end of the line, to the point that Vim has a built-in keybinding for it (and other editors possibly too). In a language that requires semicolons, you will have to remember to re-add the semicolon.

  • Requiring line breaks is a form of preventing programmers from writing unreadable code. With semicolons, you're implicitly saying that you don't care about readability.

  • There is one less character you could possibly use for some other purpose.

$\endgroup$
0
1
$\begingroup$

The semicolon can serve a purpose

In addition to the excellent answers already provided, I'll point out that in many languages (specifically the ones used for scientific computing), lines ending in a semicolon will suppress printout, whereas lines without a semicolon may automatically print things to standard out. For example in MATLAB/Octave/Julia:

A = 5;
B = 6;
C = 7
D = (8 + 9)*bsxfun(@times,A,B)
E = sqrt(5)*factorial(6)
F = (8 + 9)*bsxfun(@times,A,B);
G = sqrt(5)*factorial(6);

Prints out:

C = 7
D = 510
E = 1610.0

Notice that A and B and F and G are all not printed, because of the semi-colons :)

$\endgroup$
4
  • 3
    $\begingroup$ The purpose of semicolons and print-silencing seems irrelevant for a question about line-endings. $\endgroup$
    – Brian BED
    Commented May 16, 2023 at 21:51
  • $\begingroup$ My answer talks about line-ending with semicolons vs line-ending without semicolons, and what the difference in output would be with at least three different languages. $\endgroup$ Commented May 16, 2023 at 21:55
  • $\begingroup$ Its not. The question is actually about statement endings not just newlines. $\endgroup$ Commented May 30, 2023 at 16:59
  • $\begingroup$ LOL. It was only two minutes after your comment on my answer, that you wrote the following comment on the original question: "You mean the end of statements (or similar) not the end of lines I think. Lines can be continued." On one hand you "think" you know what the question was, and on the other hand you're telling me with supreme confidence what the question was. $\endgroup$ Commented May 30, 2023 at 17:27
1
$\begingroup$

In the first version of AEC (one that compiles to x86), I was using new-line as a statement terminator. Now I think that's not a good idea, so, in the second version of AEC (one that compiles to WebAssembly), I am using ; for that.

Simply put, I think being able to break a long statement into multiple lines makes the code much more legible. Consider this statement from the x86 dialect of AEC:

        If (distance < (r + 0.5) ) | ( (y = 12) & (x > 40-1) & (x < 40 + (13 * 2) ) )

It would have been a lot more legible if you could break it into multiple lines, right?

You can read more about the differences between the two dialects of AEC on my blog.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .