Design Note: Implicit Semicolons (2017)
craftinginterpreters.com
craftinginterpreters.com
It is painful when I need to use them in another language.
After coding for decades, I’m glad to have one less thing to type.
If we want verifiable code, let’s move up a level of abstraction and build safer languages.
I have no idea why this is.
class Foo {
...
};The reason it's confusing is because if you modify it very slightly, then the semicolon becomes required:
let Bar = class Foo {
...
};
In that context, precisely the same sequence of characters as you had before is now called a ClassExpression, and the semicolon isn't attached to the class, but instead to the preceding let.I do find semicolons useful as an indicator at the end of the class expression that there's a let or a const somewhere above, but it's far simpler to just not do that and pretend that language facility doesn't exist unless you really, really need it.
If there are conceptual semicolons, they should be visible.
A language that is semicolon free should not have a rule for invisible/phantom/imagined semicolons, that is not the way to free the programmer from thinking about it.
So, why require them? The only reason I can think of would be readability, but I haven’t seen a language where that is a serious concern (scala gets close with its “there must be a blank line here” at times)
I think “no semicolons” nowadays is the option to go for.
So that there's a step in between where the human can check the result
This is, for example, a major philosophy behind Rust's well-regarded error messages. The compiler rarely makes this kind of choice behind your back- instead, in reports an error message with a suggested fix-up for what it thinks you mean (and editor integration can apply those automatically).
Then the program source remains unambiguous and clear, there are fewer ways to confuse readers, but you still get all the benefits of "if your editor can do that, so could the compiler."
Far too much thought and angst goes into things that should not need to be a concern. I used to feel more dubious about such tools, but after gofmt I tried Prettier and always use it now.
It's just one more bit of cognitive load that is gone. Indeed, if it doesn't reformat my code as expected I know I've made an error, so it's even quicker for me at noticing some bugs than ESLint messages which are already pretty excellent these days.
I used to spend time configuring formatting settings to be exactly right, but nowadays I’m moving towards defaults.
My only problem with gofmt is that since Go standardized on tabs, I’ve encountered problems with noisy commits because people rewrap comments, and the comments will wrap differently depending on the individual’s tab width setting.
On the other hand, I worked exclusively in C-like languages until about five years ago (I almost exclusively use Python now) and I made the opposite observation. In English articles, indented lists and paragraph breaks really pop, helping me understand the structure of what I'm reading at a first glance. When I realized Python's whitespace gives me the same thing, I find it hard to go back to curly braces defining blocks unless there's an additional convention of whitespace-indentation on top.
python3 -c 'import math; tau=math.pi*2; r=1; u=r*tau; s=r*r*tau/2; v=r*r*r*tau*2/3; print(u,s,v)'before_loop; while something: step1; step2; after_loop;
Note that after_loop is still inside the loop. Also you can't have multiple colons on the same line, so this doesn't work either:
if test: do_this; else: do_that
That gives a syntax error
One of the funny things about Go’s automatic semicolons is that you can easily trip up gofmt if you type sloppy code and expect gofmt to clean it up for you. For example, let’s say you type this:
func foo() {
if x < 3 {
return 1
} return 2
}
You think, “Oh, I missed the enter key when I was typing it but gofmt will fix it for me.” No, it will not. Syntax error.That's not true, Python has semicolons and they're invaluable for composing `-c` command line scripts in a whitespace sensitive language
first
-second
will either be (- first second)
or (though this is a bit silly) (do
first
(- second))
and there is never any danger of mistaking one for the other.However Oil uses {} for blocks, and it also has dicts.
Originally I thought we should have something like %{} for dicts to give the lexer and easier job when deciding when newlines are significant. Oil uses Python's rules of newlines being ignored between () and [], and it would make sense to ignore them inside {} but not %{}.
But I found I could just makes newlines significant within {} and then put them in the grammar for dicts, as well as for blocks. It seems to work fine.
http://www.oilshell.org/blog/2020/10/big-changes.html#dicts-...
I am open to feedback if anyone wants to kick the tires :)
You can also override the default evaluation strategy of `CompoundExpression` if you `Unprotect` it first, or you can define upvalues so that your own things behave differently when they appear inside `CompoundExpression`s. It's all very lovely.
SetAttributes[Parallel, HoldAll];
CompoundExpression[x__Parallel] ^:= ParallelCombine[Identity @@@ # &, {x}]
In[28]:= (Parallel[Pause[3]; 3]; Parallel[Pause[3]; 4]; Parallel[Pause[3]; 5]) // AbsoluteTiming
Out[28]= {6.00864, {3, 4, 5}}A silly idea I was thinking about is supporting
With[{x = 2}];
With[{y = 3}];
x + y
for With[{x = 2},
With[{y = 3},
x + y]]
to reduce nesting. SetAttributes[Have, HoldAll];
Have /: HoldPattern[CompoundExpression[x___, Have[s_Set], y__]] :=
CompoundExpression[x, With[{s}, CompoundExpression[y]]];
In[698]:= (Have[x = 2];
Print[x];
Have[y = 3];
Print[y];
x + y)
During evaluation of In[698]:= 2
During evaluation of In[698]:= 3
Out[698]= 5 With[
{x = 2},
{y = 3},
x + y
]
Though this form is not (yet) documented.Crucially, y could refer to x here.
It can really help with the indentation.
Something I've wondered: would it be possible to support With[x = 2, x + x] when there's just a single variable? It's certainly very low-priority, but it saves a few keystrokes when doing quick calculations.
There was actually a massive internal discussion in 2015 re: With[x = 2, x + x]. And With[x = 2, x + x] actually did work for a time.
Unfortunately, several other ideas like With[x, x+x] and Module[x, x+x] (which was unintuitively NOT implemented as Module[{x}, x+x]) also got thrown into the conversation and things became confused, these uses were seen to be problematic, and soon the baby was thrown out with the bath water.
So technically yes, With[x = 2, x + x] could work, but it is unlikely that it will ever be implemented in product.
Another remark is that Python parsing can be hard as a single newline can actually end multiple blocks. Lexers are not designed to have a single char denote multiple tokens.
Parsing the offside rule -- I know that's not actually Python -- can be done straightforwardly with a parsing combinator transformer (originally due to Hutton?) if tokens are associated with layout information.
Pascal however used BEGIN and END to denote the beginning and the end of blocks. Maybe your memory got garbled.