Drop indents, embrace braces

Have went public last week, and, since then, lots of people have shown interest in it. I’ve been a bit overwhelmed by the scale of the feedback I had received, much of which was - maybe surprisingly - very constructive (or maybe that’s just the quality of the Go community).

Anyway, I had a few days to process it, and made some decisions. One them is a now-or-never one, so I want to announce it as soon as I can.

In short, Have is dropping the indentation based syntax. It was the most controversial thing that people were complaining about, and some of them gave good arguments against it, which eventually got me convinced. I’ll describe the reasoning behind the change, and then add a few bits about the implementation.

Why?

While it’s hard to talk about personal feelings towards language syntax (and I won’t do that), there are objective reasons for the change.

The gofmt tool is ubiquitous in the Gophersphere, any Go developer with more than a day of experience uses it. It’s famous for its non-configurability, which made Go one of the few languages without never ending disputes about formatting.

That could - in theory - be done for any language, including Have. Enforceability of a common standard wasn’t a problem - but Go users rely on gofmt more heavily than just that. They often paste or write code that has messed up formatting, yet is valid, and just run gofmt to fix it, usually on save (I do that too, actually). It means that gofmt took some of the responsibilities that were usually associated with the editor/IDE.

Something like that can’t be achieved with a language where indentation is meaningful, and Have would need to rely on editors. It’s not a problem per se, but Have - even though a whole new language - is meant to be just a helper for Go developers. Requiring completely different usage patterns would end up being a hurdle.

There were other potential sources of friction, too. For example, one was that braces-based code is easier to generate, or another one from a Vim user who complained that she presses %, while the cursor is over { or }, to navigate to the other end of a block.

Eventually, I ended up with a list of habits that people would need to change, without giving them a good reason for it. The focus of Have should be on adding features that are useful, and indentation based syntax seemed superfluous.

How?

The change will be mostly obvious, code blocks will look just like in Go, also comments syntax will change to match what you would expect in a C-like language. I started working on it a few days ago, and only modifications in lexer and parser will need to be made (plus all the test cases), type checker and code generator don’t need to be touched. And finally, there are two specific things that should be mentioned.

Semicolons

Go actually uses semicolons to separate statements, however, they can be usually omitted in the code, and lexer will insert them internally during compile time, using a simple rule:

If the newline comes after a token that could end a statement, insert a semicolon. (Effective Go)

It’s known to cause confusion among newcomers. For example, it makes the following snippet invalid code:

if true // Go inserts a semicolon here
{
}

That’s not a big problem, but Have will just parse line breaks appropriately instead.

Complex literals in control clauses

This is a lesser known issue, so I’ll give a more lengthy explanation (and, besides, it’s just interesting trivia).

Consider this Go code:

for x := range []int{1, 2, 3} {
	print(x)
}

It’s a simple loop over a slice. If you made a custom named type from that slice type, and wanted to iterate over its literal, just like in the code above, it wouldn’t compile:

type List []int
for x := range List{1, 2, 3} {
	print(x)
}

Why? Well, before I answer that, take a look at another snippet:

var List = []int{1, 2, 3}
for x := range List {
    print(x)
}

The second line is the same as the beginning of the second line in the previous example (except formatting). The parser doesn’t know what List is, so the brace that follows it could mean both the beginning of a literal or a code block. And in that situation, Go chooses the latter, getting lost in the code as a result.

Of course, this isn’t limited to slice literals and loops, this doesn’t work either:

type Point [2]int
var p = Point{1, 2}
	
if p == Point{0, 0} { // Of course, "[2]int{0, 0}" would've worked just fine
	fmt.Printf("(%d, %d)", p[0], p[1])
}

(If that happens to you, just put the literal in parentheses.)

What would it take to fix this behavior? If language changes were possible, C-style syntax, where control clauses are put in parentheses, would work (right now it works in Have too, parser knows that a clause ends when it sees a colon).

If altering the language is out of question, fixing it would require surprisingly much. There are two ways:

  • In the above examples, parser could know what List and Point are. But that would shatter the code isolation between parser and type checker. Right now, Go topologically sorts objects (in Go-speak, objects mean something else) before type checking, so that interdependent definitions are checked in the right order. That would need to happen during parsing, not exactly before, but not exactly after it. Beside simple idents, things from other packages would need to be handled as well.
  • Backtracking. Anonymous blocks would complicate things, but the requirement to separate statements with a semicolon (either actual or virtual) could help. But Go is intentionally close to being a regular language (or maybe it is one?), so backtracking is probably out of question, too. That’s one of the things that make Go compilation fast - it doesn’t need to walk back and forth in the code, it’s all done in one scan.

Both sound very daunting. My opinion is that unless it is possible to change the language (and it isn’t), leaving it as it is right now is the right decision. Therefore, even though it’s a bit irky, Have will inherit this behavior. What I can do is to try printing a better compile error, but that’s it.

Summary

As I wrote, I have already started preparing the code changes. It will probably take a few days more, depending on how much spare time I get, but it shouldn’t be long. And the website will be updated right after writing this post.