New blog post!

I've been investigating out how various languages get away with not requiring semicolons.

I looked at 11 languages and found so many interesting cases I had to share!

https://terts.dev/blog/no-semicolons-needed/

#programming #roto

No Semicolons Needed | Terts Diepraam

@terts I would urge you not to go down this road. Stick to mandatory semicolons. If it were entirely my call, I'd take a step *farther* toward mandatory statement terminators, and make

fn returns_b() -> rettype {
a();
b()
}

a syntax error; you would be obliged to write

fn returns_b() -> rettype {
a();
b();
}

and that would return the value returned by b, unlike Rust and current Roto. To return nothing, you would write

fn returns_unit() {
a();
b();
();
}

@zwol Could you explain why you feel that way? Do you like the explicitness? Do you fear the ambiguity even in the best approaches in this post?
@terts crossed messages, see my self-reply

@terts This is based on extensive experience with C, Rust, Python, Perl, awk, sh, R, and Javascript, and some exposure to Ruby, Go, and Lua (all of which I can read, but avoid using for unrelated reasons).

My experience has been that only the extremes - Python's "the indentation _alone_ determines block structure; use semicolons only to cram multiple statements onto the same line" and C's "you must put a semicolon at the end of every statement" - avoid confusing people with edge cases.

@terts Notably, the R rule that it seemed you liked pretty well, has the nasty consequence that if you want to break a line at an operator (very long chains of expressions are common in R programming due to its |> pipelining operator) you must either parenthesize the entire expression or break the line _after_ the operator. In practice, people break the line after the operator, which I find less readable than breaking it before the operator.

@terts And I dislike Rust's "leave off the last semicolon to make the block evaluate to the value of the last expression instead of to ()" rule because that makes the value of the block change depending on the presence or absence of one character that otherwise has minimal semantic significance, so your brain learns to ignore it.

The type checker will usually flag this when you get it wrong; it would be a much worse problem in a language where bugs like this are runtime errors.

@terts And finally I would argue that the sheer _variety_ of approaches to deciding where an expression ends, in the absence of an explicit terminator, is itself a reason not to go there. Because no matter what you do in Roto, it's going to be different from at least a few other languages, and that'll be a trap for people coming from those languages.
@zwol Yeah you're not wrong there. It definitely something to consider. This post was an exploration of what's out there and I only want to implement something I feel confident in.
@zwol On R: I agree. Somebody on Reddit proposed a `..` at the start of the next line as an alternative. The reason I liked R is mostly that it's unambiguous and simple. I don't think I'll emulate it.
@zwol On Rust's semicolons: I agree that this feature shouldn't exist in a more dynamically typed language. Funnily enough, I think Swift kind of has what you describe but with no lines having semicolons, so all lines are treated equally, but the last expression can be the value that a block evaluates to. They also pull some tricks to not always require the trailing `()` (for better or for worse).