The original version of this post was here. I have made adjustments and fixes after further review of the spec and listening to feedback online. Thanks to everyone who responded!

The Generics Draft

The design draft for generics in Go 2 has been published for some days now and it’s no surprise given the immense amount of history that has taken place. Go already supports type switches and interfaces that enable very loose generic code, but still another kind of generic behavior is desired enough to bring about this draft.

I intend to demonstrate that the draft for contracts proposes an unfit solution to generics on the grounds that it breaks several key features of the language for little benefit.

Highly Variable Function Signtures

func Print(type T)(s []T) {
}

Above is a simple example of the proposed new set of optional parameters that are used to declare the use of a contract. The signature of a func in Go is currently easy to read due to the careful placement of things. With this new optional parameter, you can no longer visually use the position of signature elements and must carefully identify the lengthy type parameter to know what this signature says.

Here are 3 examples of function signatures. Can you tell which one is using a contract easily?


func One(x string, b bool)(err error) {
}

func Three(x string)(bool, error) {
}

func Two(type T)(x string, y T) error {
}

Now imgine you are a new programmer. All the optional parts of a signature are appearing and disappearing between functions. That would be really confusing and heavily goes against Go being “easy to learn”.

Too Similar to Interfaces

Here is a quote from the design draft:

“In this design, a contract describes the requirements of a set of types. We‘ll discuss contracts further later, but for now we’ll just say that one of the things that a contract can do is specify that a type argument must implement a particular method.”

This is just an initial example, but already this is far too close to an interface. When do you use an interface? When do you use a contract? The lines are too blurry.

Worse yet, if you make the mistake of choosing the wrong one, it won’t be simple to refactor. You’ll have to find and change the function signature for anything that implements a contract (or the inverse). If your code is a package, this would be a breaking change to your consumers.

Furthermore, I think it’s likely that many projects will end up overusing generic functions. Generics have a pretty small use case, but I can imagine plenty of siutations where implementing as many funcs as you can with a contract becomes normal.

Function Signature Bloat

func signatures become much more jumbled and messy with the addition of this new contracts section added in. Big function signatures increse cogntitive overhead and make us worse programmers. Here are some examples of realisticly-sized signatures contracts would bring:

func (u *User) AsString(type T stringer)(s []T, b bool) (ret []string, err error) {}

func (c *Cat) Meow(type T petSoundContract)(cats []*Cat, voice []T) (Sound, error) {}

func (c *Car) Drive(type T1 selfDriving, type T2 autoNavigation)(directions Directions, drivingCPU []T1, navigationCPU []T2) (t *Trip, err error) {}

Let’s not forget that a contract also has a spec that you have to click into and understand. Now try thinking about some situations that implement a contract AND satisfy an interface. This is a large increase in things to think about, which translates to complexity.

Crowding of the Namespace

The Go namespace for all base level functions, structs, variables, and interfaces is already rather croweded. Go only has packages to divide all of these things. To help with this, a couple naming conventions emerged that let you identify what things are at first glance:

  • strings and errors are packages. string and error are types. Without proper IDE hilighting, this is invisible to the user. Adding an s normally means a package reference.

  • stringer and io.Writer are interfaces. Interfaces normally end in an er.

This means that the error type does not collide with the errors package and so on. Are we sure we want to add contracts into this arena?

The Proposal is Huge

This proposal for this change is LONG - coming in at 14,561 words. Even if you could read continuously without thinking about the examples, it would take the average college student 32.5 minutes to get through the proposal. It’s too long. If we want consideration and feedback, we should keep things easily digestable.

One of my favorite quotes from Einstein comes to mind here:

If you can’t explain it simply, you don’t understand it well enough.

While the authors of this proposal did a good job considering every possible use case, I think the sheer size of this document shows that the implementation of contracts can be nuanced and is in my opinion, too user-hostile.

Go has always taken the approach of making small changes and waiting for the next steps to become clear. That does not feel like what we are doing here. Finding a simpler solution is harder, but that should be the current challenge at hand.

Closing

For the reasons above and more, it is my opinion that contracts (as proposed) are too complex to be worth it.

In closing, we as the Go community should be very careful not to accidentially listen to the loud minority. Go offers an excellent and easy to understand set of tools to solve coding problems already and we shouldn’t add anything unless we’re sure fixing a really large shortcoming.

« Back to Article List

Comments

comments powered by Disqus