Hacker News new | ask | show | jobs
by monkeyfacebag 2190 days ago
On <> syntax, this has been discussed quite a bit. () were chosen to avoid ambiguous parsing.

Also note that generic methods are not allowed in the current design, only generic functions.

The new draft design ( https://go.googlesource.com/proposal/+/refs/heads/master/des... ) discusses both of these points. I recommend everyone who is interested in this topic read the design spec, ideally before commenting.

3 comments

On the <> syntax, why not improve the parser? Every language that uses <> for generics has a parser that is able to distinguish between generics and the "less than" operator, and it seems odd that Go developers think it can't be done efficiently.
C++ and Rust require extra disambiguating syntax in some contexts (C++ the `foo.template bar<T>()` syntax, Rust the "superfish operator" `foo.bar::<T>()`. Java works around the problem by awkwardly putting the generic argument list in front of the method name (`foo.<T>bar()`). I don't know about C#.
C# is `foo.bar<T>()`, I’m not sure what compromises that had to make for that to work but from an end-user perspective it works well.
They parse it one way, and if they figure out the other parse option was right, they go back and fix the parse tree.

Basically, unlimited look-ahead.

Is it somehow a problem?
(teeny tiny note: turbofish, not superfish)
Oops :D
Using [] is the optimal choice for languages in general, but they can't even use that because they blew away that syntax for indexing.
Scala uses [] for generics and () for indexing, which sort of makes sense given that in the end arrays are just functions.
So are the uninstantiated generic types ;-)
Go developers like to claim that the reason the Go compiler is fast is because it's "simple to parse". Unfortunately this doesn't make a lot of sense as parsing is typically only 1-5% of the total compile time.
It's not only about the main compiler. Go has tons of third-party tools (linters mainly) that can parse go code, because it's so easy to write one. Most of them wouldn't exist if parsing was a PITA.
Those almost universally use the "ast" package provided by the stdlib.
It is not a question of "Improve" but a question of dealing with tradeoffs. The Go parser is built so that at every stage it is totally unambiguous what the parser has to do.

This reduces the amount of state that the parser has to carry around and makes the error messages for syntax errors easier to generate.

Languages that use <>'s for generics have to look at a larger amount of the code when parsing to work out what to do.

In my opinion, the new design doc clearly characterizes this as a design goal of the parser, rather than a hard constraint that cannot be resolved.

"Resolving that requires effectively unbounded lookahead. In general we strive to keep the Go parser simple."

It's totally fair to question the tradeoff - should having a simple parser outweigh the potential ergonomic benefit of <>? I don't know. The forum for this is probably the golang-nuts group.

The design doc doesn't say "simple", it says "efficient".

I think "simpler" would have been a stronger argument. While using <> would make the parser more complex, I have a hard time seeing that making a meaningful performance difference in a compilation context. Maybe if you're parsing a lot of Go without actually compiling it, but that doesn't seem like a use case to optimize for.

It's all a matter of where you accept the complexity - in the compiler, in the language, or in the downstream applications. In my view it's an obvious choice to choose a more complicated compiler on exchange for simpler downstream applications...
The main reason is probably that by omitting <> for generics makes the symbol table unnecessary as part of the compilation stage. Go and D are the two modern languages that are avoiding symbol table like a plague for faster compilation time, i.e. the code should be parseabled without having to look things up in a symbol table .
The golang approach has been to dumb down the parser, at the expense of making it more complex for users.
there is no complexity tradeoff for users here.
Angle brackets are easier to parse for humans.
Ah, right, I missed this:

https://go.googlesource.com/proposal/+/refs/heads/master/des...

Hmmm ... not sure how I feel about that. Ok so my original example becomes this:

  func (obj *SomeType) Foo(type K, V, Q comparable)(key K, val V) (*OtherType(Q, V), error) {
      ...
  }
Which is only slightly better [without the generic type].

Don't get me wrong, I'm a big fan of Go, but I'm kind of on the fence about generic types. I've made do with casting interfaces and type-casts for many years and I'm OK with it (honestly, glad to not be a C++ or Java programmer anymore).

> Ok so my original example becomes this

No, you still don't understand. Methods can't have additional type parameters, only functions. This part is not allowed in your "example":

    (type K, V, Q comparable)
The doc says:

> Generic types can have methods. The receiver type of a method must declare the same number of type parameters as are declared in the receiver type's definition. They are declared without the type keyword or any constraint.

So my original example should have been:

  func (obj *SomeType(K, V)) Foo(key K, val V) (*OtherType(K, V), error) {
        ...
  }
(hopefully this is now correct!).
Exactly. Thanks!