> Because composition doesn't intermingle objects the way inheritance does, it allows much more interesting "chains" of objects to be created that extend each other, without the complexity exploding.

Go's (really Plan 9 C's) composition support does "intermingle" methods, which has pretty much the same effect as intermingling fields. To see this, note that Go needs rules about who wins if methods for an outer object and an inner object have the same name (and they have to consider diamonds, which you can have with composition!)

> So, "Go doesn't have generics" is only half true.

I know this is going to be me annoying about terminology, but I really disagree. :) Go has polymorphism. It doesn't have generics, at all. "Generics" has a specific meaning: the ability to take types as parameters.

> It can be profitably argued that the primary virtue of "generics" is generic algorithms rather than generic types.

I don't agree. Generic types are necessary, because you often can't express generic algorithms naturally without generic types. Just as an example of where I needed generics in my work most recently, I wrote a polygon clipper over the past couple of weeks that has special fast paths when it clips rectangles and falls back to Sutherland-Hodgman clipping for complex polygons. This was natural to express by making the clipping result generic over the type of polygon (rect or edge list). I could maybe have done it in Go by using the "container-as-interface" trick that the sort package uses, but that would introduce a lot of boilerplate.

There's also the issue that all these interfaces make it awfully hard for the optimizer to produce as good code as it would if you actually had generics. Unless you inline everything away (in which case SROA and constant propagation can mostly get you there), you'd need a nontrivial interprocedural analysis. Performance issues around generic programming tend to have outsized effects, because functions that tend to be generic are precisely those functions that tend to be called a lot (because they're generic!) and shoot to the top of the profile.

> As a side note, if Go ever does grow full generics, I expect it to do so by extending the half it has, rather than adding a new concept of generics on the side. It's easy to imagine a generic BalancedBinaryTree that requires its nodes to conform to a LessThan interface.

This I agree on entirely. Note that this is pretty much exactly what Swift did :)

> If you have a constrained set of messages, for instance, you can use the type system to more carefully declare them as coming from a particular set of types, not just interface{}.

But you lose half of the point of sum types, in that the compiler can check exhaustiveness for you. It's worse, because since you're doing a Go type switch, you can match against cases that aren't even in the sum type and the compiler won't complain! It's also an awful lot of boilerplate to create a half-approximation-of-a-sum-type, meaning that the ergonomics favor people not using it. interface{} is just plain easier to use compared to the sum type pattern, which is why it gets so much use.

> Go still manages to cover so much with its meager feature set. There's a lesson here for language nerds. I don't fully know what it is yet, but I'm working on it.

I think the lesson is "people will figure out design patterns to approximate things that your language doesn't support". This is no surprise: people write OO systems in the C preprocessor. But I think it would be a mistake to conclude "omit useful features from the language if there's any way people can possibly approximate them". This was the approach that Java famously took, and people rightfully soured on the extreme emphasis on "design patterns". And what you've described in this post is nothing more than a set of design patterns. Design patterns have their place and are very useful, but they're also the sincerest form of feature request.