|
|
|
|
|
by ianlancetaylor
2847 days ago
|
|
Thanks for the comments. We tried for some time to declare contracts like interfaces, but there are a lot of operations in Go that can not be expressed in interfaces, such as operators, use in the range statement, etc. It didn't seem that we could omit those, so we tried to design interface-like approaches for how to describe them. The complexity steadily increased, so we bailed out to what we have now. We don't know whether this is is OK. We'll need more experience working with it. I'm not sure that Go is a language in which higher-level abstraction is appropriate. After all, one of the goals of the language is simplicity, even if it means in some cases having to write more code. There are people right here in this discussion arguing that contracts adds too much complexity; higher order polymorphism would face even more push back. |
|
I guess it’s those very constraints that get to me. This is going to sound harsh, but I believe the goal of “simplicity” is already very nearly a lost cause for Go.
The language is large and lacks any underlying core theory, with many features baked in or special-cased in the name of simplicity that would have been obviated by using more general constructs in the first place; the language is not simple, it’s easy for a certain subset of programmers. Adding convenience features here and there and punting on anything that seems complex by making it an intrinsic has led to a situation where it’s increasingly difficult to add new features, leading to more ad-hoc solutions that are Go-specific. This leads to the same problem as languages like C++: by using Go, developers must memorise details that are not transferable to other languages—it’s artificial complexity.
A major source of this complexity is inconsistency. There are few rules, but many exceptions—when the opposite is more desirable. There are tuple assignments but no tuples. There are generic types but no generics (yet). There are protocols for allocation, length, capacity, copying, iteration, and operators for built-in containers, but no way to implement them for user-defined types—the very problem you describe with contracts. Adding a package dependency is dead-easy, but managing third-party dependency versions is ignored. Error handling is repetitive and error-prone, which could be solved with sum types and tools for abstracting over them, but the draft design for error handling just adds yet another syntactic special case (check/handle) without solving the semantic problem. Failing to reserve space in the grammar and semantics for features like generics up-front leads to further proliferation of special cases.
Virtually all of the design decisions are very conservative—there’s relatively little about the language that wasn’t available in programming language research 40 years ago.
I don’t mind writing a bit of extra code when in exchange I get more of something like performance or safety wrt memory, types, race conditions, side effects, and so on. But Go’s lack of expressiveness, on the whole, means it doesn’t provide commensurate value for greater volume of code. But it could! None of these issues is insurmountable, and the quality of the implementation and toolchain is an excellent starting point for a great language. That’s why I care about it and want to see it grow—it has the immense opportunity to help usher in a new era of programming by bringing new(er) ideas to the mainstream.