Live data from Hacker News

Why Generics?

blog.golang.org

251–260 of 261 posts

Re: Why Generics?

#252
post #234

Earlier quoted context omitted.

Scheme?

I fleshed my comment out into a blog post yesterday, which got some interest[0] on Lobsters. It appears that modern Scheme is easier to write portably and an even nicer compromise than I had originally thought. [0]: https://lobste.rs/s/ytuyya/perfect_language_why_go_still_isn...

Scheme is possibly the best designed language I've ever seen. Which may be why no one uses it.

(Note: Macros, even Scheme's hygenic ones, can produce horrible monstrosities. Like loop. And the urge towards object systems gives Scheme a horde of them, mostly bad. This may be a lesson in getting what you ask for.)

Re: Why Generics?

#253
post #233

Maybe this is unfair, but it’d be great if the Go devs could just say “generics will work similarly to [C# / Java / Swift / D / whatever], except that we’ll address [problems] with [adjustments]”. Rather than going through this whole rigmarole of resisting adding generics too early because all existing implementations are bad, then slowly reinventing the wheel from scratch, then finally ending up with something prett…

> if the Go devs could just say “generics will work similarly to [C# / Java / Swift / D / whatever], except that we’ll address [problems] with [adjustments]”. This is basically how C++ was designed, and it turns out not to work very well; the [adjustments₁] for [feature₁] turn out to introduce not only unanticipated [problems₁] with [feature₁] itself but also new and previously unimagined [problems₂] with [feature₂].…

I don’t agree with your point, but I applaud your use of Unicode.

Re: Why Generics?

#254
post #233

Earlier quoted context omitted.

> if the Go devs could just say “generics will work similarly to [C# / Java / Swift / D / whatever], except that we’ll address [problems] with [adjustments]”. This is basically how C++ was designed, and it turns out not to work very well; the [adjustments₁] for [feature₁] turn out to introduce not only unanticipated [problems₁] with [feature₁] itself but also new and previously unimagined [problems₂] with [feature₂].…

I don’t agree with your point, but I applaud your use of Unicode.

Well, consider C++ inheritance combined with object nesting; you get slicing copies.

Or constructors combined with static objects; you get the static initialization order fiasco.

Or constness with template functions; you get two, four, or eight copies of each generic function in your source code according to which things are const.

Or (compile-time) overloading with (run-time) overriding; you get C++’s weird “hiding” rule about the other overrides you didn't override.

Or separate compilation with implicitly instantiated templates; you get geological build times as the compiler instantiates the same templates in every .C file and then throws away all but one of the identical instantiations at link time. (To be fair, this is far from the only reason C++ compiles slowly.)

Overriding combined with type conversions through implicitly invoked constructors (and implicit referencing and implicit casting to const) gives you annoying bugs that are unnecessarily hard to figure out.

Cleanup from exceptions via RAII combined with C’s unspecified argument evaluation order led to a situation where resource leaks during certain kinds of operations couldn't be avoided reliably, a bug in the language definition that wasn't noticed for several years, though I think it's fixed now.

The grammar is undecidable because of the number of different things that have been added, which sounds like a hyperbolic joke but is actually literally true, and a significant obstacle to implementing something like gofmt for C++.

The combination of template parameters using for parameters, the traditional longest-leftmost tokenization rule, and the >> operator for bitshifting made foo> an unexpected syntactic pitfall, one that is now fixed.

There wasn't an exception-safe version of the STL for a number of years, which isn't really an interaction between templates and exceptions —a non-template container had to deal with the same exception-safety problems— but it did mean that fit quite a while you could use the STL or exceptions but not both.

This is far from the extent of the problem. The C++ FAQ consists largely of affirmations of the form, “Doing [reasonable thing 1] works, and [reasonable thing 2] works too, but if you do them both, you will die horribly for your immorality.” It's exaggerating about the death part but the surprising problems are quite real.

I don't hate C++, and I think it's the best existing language for some problem spaces, but it's very much the poster boy for unexpected problems arising from interactions between features.

As far as the keyboard goes, https://GitHub.com/kragen/xcompose and http://canonical.org/~kragen/setting-up-keyboard are your friends.

Re: Why Generics?

#255
post #254

Earlier quoted context omitted.

I don’t agree with your point, but I applaud your use of Unicode.

Well, consider C++ inheritance combined with object nesting; you get slicing copies. Or constructors combined with static objects; you get the static initialization order fiasco. Or constness with template functions; you get two, four, or eight copies of each generic function in your source code according to which things are const. Or (compile-time) overloading with (run-time) overriding; you get C++’s weird “hiding”…

I generally agree with that characterisation of C++, but it’s not at all what I was trying to get at by suggesting that Go generics should copy ideas from other languages.

It’s good that they’re being careful and conservative; but I feel that they’re going out of their way to avoid all existing paths, whereas it would actually be safer to use some existing system(s) as a starting point, as both the advantages and disadvantages -- in practice, not just theoretically! -- are known.

I definitely agree that interactions between features need to be carefully thought through. One likely wart with the current proposal is that user-defined generic types still won’t look or behave quite like the built-in ones, as those have special syntax and special operators. Will it be considered good practice to expose raw slices, maps etc in APIs? Or will people start wrapping them (just as in Java, an array would most often be wrapped in an ArrayList for convenience)? This might have been easier to resolve if generics had been thinkable much earlier in the language design.

Re: Why Generics?

#256
post #254

Earlier quoted context omitted.

Well, consider C++ inheritance combined with object nesting; you get slicing copies. Or constructors combined with static objects; you get the static initialization order fiasco. Or constness with template functions; you get two, four, or eight copies of each generic function in your source code according to which things are const. Or (compile-time) overloading with (run-time) overriding; you get C++’s weird “hiding”…

I generally agree with that characterisation of C++, but it’s not at all what I was trying to get at by suggesting that Go generics should copy ideas from other languages. It’s good that they’re being careful and conservative; but I feel that they’re going out of their way to avoid all existing paths, whereas it would actually be safer to use some existing system(s) as a starting point, as both the advantages and dis…

Oh, to that point I agree. I feel like that's not too far from what they're doing, really.

I agree that, with this design, it won't be as comfortable to use generic containers from a library as to use the built-in containers. That's been a persistent problem with C++ templates and Java generics, too, though (I think reasonable initializers for vector finally landed in C++17?) so maybe the lesson they took was that the base language should have slices and maps because there was no known reasonable design that allowed them to be seamlessly defined in a library while supporting static typing and object nesting? Maybe there is a solution if you design the language around generic containers from the beginning—have you tried D?

Re: Why Generics?

#257
post #143

Earlier quoted context omitted.

> The only truly unforgiveable one is UnsupportedOperationException. The guy who wrote the collections API for Java didn't know the first thing about the Liskov Substitution Principle, and gave the world an implementation that violates it... The implementation perfectly follows it because the LSP simply requires identical behavior, and the contract clearly states it may[1] throw an exception. All the implementations…

No they most definitely are not. One implementation receives a message. The other categorically rejects it. I cannot substitute them.

Yes, those are the same behavior. Both of them "may" accept it and "may" reject it, and "may" includes both "always" and "never".

It is correct to the letter of LSP, but not the spirit of it, and this was a conscious decision; contrary to your original claim, they understood the principle perfectly well.

Re: Why Generics?

#258

Earlier quoted context omitted.

Every project I’ve written so far in golang could benefit from generics.

but what is the benefit? How does your code change. Generics are a tail abstraction. Its the last abstraction that you are able to make to your code to reduce boilerplate. This usually means that is the least useful and in generics specific case the boiler place it reduces is minimal. Its lack of impact is why I dont really care about the subject. Adding or removing generics to a project matters little. So why bother…

> Generics are a tail abstraction. Its the last abstraction that you are able to make to your code to reduce boilerplate. This usually means that is the least useful and in generics specific case the boiler place it reduces is minimal.

This is absolute and utter delusional nonsense.

Re: Why Generics?

#259

Earlier quoted context omitted.

I think it is Gilad Bracha that said, when the decision was made to include only covariance in Dart, that variance flies in the face of programmer's intuition. He's right. It's easy to understand one level of variance: you can replace a return type by a subtype and a parameter type by a supertype. (I wouldn't be surprised that many programmer don't undestand this.) Two levels already requires some deep thinking (assu…

I'm not really sure what you mean. Variance just changes what is and is not a subtype/supertype. If List is covariant then List is a subtype of List if T is a subtype of U. So then, by induction, List > is a subtype of List > if T is a subtype of U. I'm not sure what's hilariously hard to follow here...

Good point, it's a bad example because we assume we're just combining covariance which is intuitive.

Nevertheless, I have a point because:

1. Things get harder with contravariance. 2. Things get harder when you mix covariance and contravariance.

The prototypical covariance class is Producer (with method produce() returning T) while for contravariance that's Consumer (with method consume taking a T as parameter).

Assume each class has a superclass (Consumer0 and Producer0) and a subclass (Consumer2 and Producer2). Assume V extends U extends T.

Can you list the subclasses and superclasses of Consumer>?

Personally, I have to think carefully about this for a minute or so, and I've been there before a couple times.

This is not an especially complex scenario either. I've seen things get worse in practice.

Re: Why Generics?

#260

Earlier quoted context omitted.

I'm not really sure what you mean. Variance just changes what is and is not a subtype/supertype. If List is covariant then List is a subtype of List if T is a subtype of U. So then, by induction, List > is a subtype of List > if T is a subtype of U. I'm not sure what's hilariously hard to follow here...

Good point, it's a bad example because we assume we're just combining covariance which is intuitive. Nevertheless, I have a point because: 1. Things get harder with contravariance. 2. Things get harder when you mix covariance and contravariance. The prototypical covariance class is Producer (with method produce() returning T) while for contravariance that's Consumer (with method consume taking a T as parameter). Assu…

Ah, I can figure it out pretty easily for that example too but probably only because I am pretty familiar with variance and you chose really generous names. I personally find the reasoning about variance becomes a lot easier if you stop thinking about the definitions and start thinking about what you should be able to do. A producer of a subtype is technically also producing the supertype. A consumer of a supertype can totally consume the subtype.
Post reply on HN