Go: Don't Change the Libraries in 1.18
github.com
github.com
OTOH this removes much of the point to use generics, and makes working with stdlib from type-parametric code more painful.
Still great to see things improving. It took mere 11 years.
Took that much time because of little clique of people outside the go team had way too much influence in the Go community. Let see if they dump the language like they threatened to, as the result of adding generics. Of course they won't.
Generics are there if one wants to, and they aren't like Java, but ADA. ADA got a lot of things right decades ago including the way tasks work from which Go routines should have taken a bit more inspiration .
Congrats to the Go time anyhow.
If you'll recall back in the day there were several different vendoring approaches, but ultimately the go-team proposed and implemented their preferred solution.
Similarly there have been a million generics & error-handling suggestions but none of them were introduced. It's basically lots of distracting discussions, and it doesn't feel so much like a community project that is actually seeking outside discussion and ideas. (No shame in that, but the pretense is disappointing).
Personally I'm waiting for the fuzzing-support to land in 1.18. Fuzz testing is basically magical, and amazing in reporting problems even in code with "high coverage". The generics might be nice, but off-hand I don't see that I'll be needing them in the immediate future in any of my personal projects - but I fuzz-test the hell out of a lot of my projects (which largely revolve around interpreters and compilers).
If it actually were a community project, I think we would have more of the "x := make(someType) vs. x := someType{}" TMTOWTDI that you find in other mature languages, and go would be weaker for it.
...because x doesn't have y yet....this stuff doesn't build itself, and it certainly doesn't get built overnight.
To suggest generics weren’t here sooner because no one wanted to make the pull request it is just dumb.
the go project is pretty upfront with how they go about deciding what will/wont get into the project, what process to follow, etc.
Posing it as "why doesn't go have generics" is bound to be reductionist, because it's too coarse of a question, and any real implementation winds up having a lot of nuance.
the question winds up just sounding entitled and petulant though, so if someone can't be bothered to ask a well informed question about why go doesn't have generics yet, the best answer really is "because it hasn't been added.", tautological as it may be.
https://www.reddit.com/r/golang/comments/ksx4q1/what_happene...
That's simply not true.
> he was firmly saying no.
I'm sure you cannot find a single instance where he firmly says no to generics.
He wrote in the FAQ that Go might get generics one day and that they are continuing to think about it.
Here he is arguing in favor of generics: https://www.reddit.com/r/golang/comments/jditu9/what_do_gene...
And he was the one who invited Phil Wadler to help with the generics design. From the Featherweight Go paper: "Rob Pike wrote Wadler to ask: Would you be interested in helping us get polymorphism right (and/or figuring out what “right” means) for some future version of Go?" https://arxiv.org/pdf/2005.11710.pdf
Take C# for example. The `System.Collections.Generic` namespace is full of generic collections (surprise!) that allow more type safe code. If I have a `List`, I can’t guarantee there isn’t something I don’t want in there (that could cause a runtime exception I don’t catch). But if I have a `List<IFeature>`, I know that everything in the list implements `IFeature` (barring compiler bugs and unsafe code).
One of the nice things about go is it doesn’t have many abstractions and most of them are carefully thought out. I don’t want to have to inhabit somebody else’s abstractions all day at work, I want the language to get out of the way, which go does quite well IMO.
When extremely motivated, I’ll script manipulation of the AST, but that’s a pretty extreme thing to have to do.
And then the application grew and you have the guys who have to watch and keep the application online when it's business critical. They HATE abstractions. Because 7 abstractions that are used throughout the application means there's 127 cases the developer hasn't thought through, at least ten of them a ticking time bomb. That's 128 possible cases, one of which the developer has actually thought about, 5 they have verified to be reasonable.
An easy case to show what's happening is the suggestion every new developer makes. "I'll just have a thread per connection, that's easy". And yes, it's very easy to get it running. It doesn't block during dev, it handles multiple connections and generally does the job. And it's absolutely guaranteed to crash you server for 10 different reasons in production. And yet, every new developer will (and should) do it.
There's just 2 camps developers in the world, who don't agree and this won't seriously change. Learn how the "other camp" thinks and you'll do better.
But this is of course orthogonal to generics, as you can make generics friendly to tooling as well, see Java.
Is this stockholm syndrome or something? For example, JSON.
Also, the time abstraction is completely bonkers.
If you want to multiply a 500 milliseconds with a user-given value (suppose I want some number of half-seconds), you must first cast the user value to milliseconds, then multiply, so you are multiplying 500 milliseconds times (say, 4 milliseconds) to obtain 2000 milliseconds.
And don't get me started on goroutines/channels. You are supposed to "share state by communicating" and not "communicate by sharing state". That's great. But at some level, you must bootstrap knowledge about the state of the channel, which is fundamentally shared state by the low-level nature of the channel. When you've worked in systems which have really thought out carefully what it means to have a no-shared-state system, the go system looks like it's been put together by either amateurs or fools.
JSON is not related to go but perhaps you have some problem with the JSON parser? Works fine me… Personally I don’t find the time constants a problem at all, I’m not keen on the time parsing layout, but that’s relatively minor. Channels I don’t have strong opinions about and goroutines if used sparingly I’ve found a nice balance of utility and simplicity for creating threads, but I recognise I’m not really qualified to argue about them.
Incorrect. You must cast the user value to a time.Duration, since Go is a type-safe language, and like most type-safe [1] languages, its numerical operations generally require their operands to be of the same type.
You might be thinking of multiplying a user-provided value by a constant (of type time.Duration) defined by the time package, like time.Millisecond or time.Hour. I’m under the impression this is a very intentional choice to require user-provided values to be explicitly annotated with their units. Some implementations/variants of durations use nanoseconds, some milliseconds, some seconds, and requiring this assumption to be explicit in the code helps avoid critical bugs like the Mars Climate Orbiter failure. [2]
The time package (and other stdlib packages) definitely has some warts, especially the time format parsing, but I’ve always appreciated the approach taken for durations.
[1] We could get into more advanced type inference here like Rust’s From/TryFrom traits, but the debate between simplicity vs. expressiveness in Go has been retreaded here tens of thousands of times and I doubt either of us has anything new to say on the topic.
This is cargo-cult type-safety; stop a moment to consider the dimensions implied by the types.
To be clear: time.Duration is a type, and is the only part of this discussion where “type safety” is a factor.
time.Millisecond is a constant time.Duration whose value represents the duration of a millisecond. time.Millisecond et al are not a unit as in chemistry class, so you’re not supposed to get a time.Millisecond^2 by multiplying them. Just like in any other typed language, T * T -> T, not T^2 (where T = time.Duration), since it’s a type and not a unit.
time.Millisecond et al instead make you explicitly annotate the scale of user-provided durations. When you write
d := time.Second*time.Duration(input)
you are assigning to d a value of type time.Duration equal to the duration of one second multiplied by the input. You are free to not use the constants defined by the time package and instead create time.Durations from the literal number of nanoseconds in the duration, if you so choose: d := time.Duration(input)*1000000000
But since the time package already defines convenient constants for standard durations, those will be used instead and be far more explicit and readable.This has nothing to do with dimensional analysis of units, of the sort you do in physical sciences. I don’t know of any general-purpose languages that include dimensional analysis in the language or their standard library’s datetime package, but I’d be curious if you know of one (that isn’t specifically geared towards the sciences) - seems like the sort of thing Ada might include? In any case, I don’t think a language focused on keeping a simple feature set like Go should be a trailblazer here. Durations are easy.
"numerical operations generally require their operands to be of the same type"
The correct decision would have been to make the * operator be allowed to operate on a time.Duration and an integer, just as you are uncontroversially allowed to operate * on a float and an integer -- to refute your statement and go even stronger I don't know of ANY pls that require * operands be the same type.
However, that is not what go chose. And we are talking about the choices go made. This is very much NOT well thought out and very ill-considered, especially since "the right thing" is so easy.
You make a fair point, but at that point the debate is about other choices made in the Go language disallowing implicit type conversions.
> The correct decision would have been to make the * operator be allowed to operate on a time.Duration and an integer, just as you are uncontroversially allowed to operate * on a float and an integer
What should the resulting type of the multiplication operator be when applied to a float and an integer? Should the type be different if the operands are swapped? Is it acceptable for the multiplication operator to not be commutative, given that we seem to be demanding a great deal of rigor from our type system? Should we only allow this implicit conversion if type inference is not being used?
> To refute your statement and go even stronger I don't know of ANY pls that require * operands be the same type
Rust, for one, but will test out some more when I’m not on a bus: https://play.rust-lang.org/?version=stable&mode=debug&editio...
> cannot multiply `i32` by `f32`
You can impl Mul for your own types. The operands' types don't need to match.
This really comes down to a lack of experience on your part. Haskell requires both arguments to be of the same type and only in the case where it can reasonably infer from a literal that it could be coerced will it do so. OCaml, as another example, requires an entirely different multiplication operator for floats.
Requiring the same type for both arguments is not as rare a position as you've made it out to be, and not a showstopper in any case either.
Haskell is an example, where the type of * is:
(*) :: Num a => a -> a -> a
Which says that the arguments must be numeric, and of the same type. I think this is the sensible choice from a strongly-typed perspective, and some operation which allows one to multiply a time value should be a separate thing.I'm excited about the prospect of having iterators. It will enable a different, more consistent programming style.
I'm also hoping for immutable collections, even though the lack of specialization will make it more difficult to implement them efficiently. They would enable a more robust way to build concurrent systems.
they do definitely seem to have some NIH syndrome at times, but I can't say that as time has progressed that I haven't come to appreciate the decisions they've made that seemed controversial at the time.
Most likely yes, or as stated in the ticket, they'll take the existing proposal and implement it in golang.org/x/, where they've had some success fleshing out the design of new packages before incorporating into the standard library. It's worked out well, as early adopters can adopt and generally have a painless transition once included in the standard library.
I agree with iterators and immutable collections -- it's been painful working with trees in go, so hopefully that gets a bit easier now.
Honestly, I'm excited to see what will come of generic functions for channels. Being able to write a generic Dup(in chan T, out ...chan T), or a CtxRecv(context.Context, chan T) (T, error) to cut down on some boilerplate select statement.
the Go team has already made the libraries, theyre just publishing them in the /x/ namespace instead of the stdlib.
/x/ has everything that's not under the Go compatibility promise, among other things from the Go project.
I mean designing a feature in a clean room is one think, but using it a the standard library would be a good way to know if they messed up the design in some way.
> I propose we still design, build, test, and use new libraries for slices, maps, channels, and so on, but start by putting them in the golang/x/exp repository.
Though the second comment has merit too, I think it would be good to have some common abstraction right away, like we had io.Writer, io.Reader, etc. so everybody doesn't define their own, as it'll take time to crawl out of that.
Although in practice it did work out well with error wrapping which was first in libraries and then the stdlib defined an interface for them, which resulted in all libraries adopting that.
> Similarly, if constraints isn’t part of 1.18, there will be a lot of independent redefinitions of orderable types. I don’t think we’re going to learn from experience much that could change that package now.
Good call in my book, and I'm extremely excited to have generics finally. I thought they were going to ship with 2.0, but the sooner the better!
It's cool to hate on Go, it's taken over the system programming space for a reason, like it or not, and after working full time on Elixir it's hard not to think in map/reduce and other generic constructs, which were unreasonably verbose before in Go. Now the haters will have to focus on the "if err != nil" statement to pile on the language — though to be fair I'll expect some ergonomic improvement on that aspect as well, eventually.
With generics, Go will be my new Python, but with a decent dependency and deployment story, and I'll just need Zig for my low-level manual memory management needs. What's Rust?
EDIT: indeed being a bit cheeky with HN's favourite language isn't well received in this place.
IIRC that was the general feeling then but the impression has changed in the mean time ?
function foo(x: number) {
return x + 1;
}
let y = null;
foo(y); // compiler will flag this func foo(x int) { return x + 1 }
var y *int = nil
foo(y) // compiler will flag thi type MyStruct struct {
Name string
}
var-not-nil myStruct = &MyStruct{"Hello"} // this is a "not-nil" pointer variable
myStruct = nil // compiler would catch this
The idea being to allow variables to hold and pass pointers to structs like now but ensure they are never nil.Lack of enums and pattern matching is IMO the bigger issue. I miss that regularly.
If the semantics desired are merely those of a simple set, why instantiate storage for the value side of the map? An interface{} consumes 16 bytes. Instantiating a struct{}, i.e. with no fields inside, consumes 0 bytes.
I knew it was something with curly brackets instead of the map[T]bool I started using.
criticism =/= hate
Nobody "hates" on Go for the sake of it. If some people were not critical of that language, generics would have never been added at first place. I'm glad the Go team acknowledged the flaw instead of the gaslighting that has been going on for years in the go community from a few go users.
Generics were a pain point, but people have been crapping on Go on this forum while people out there are using it to build stuff. It's not a perfect language, but if you read any HN thread about it it's like it's impossible for people to go past the lack of generics or the existence of nil.
The existence of nil is also a design flaw. But you're right that no language is perfect and neither of these flaws keeps people from building tons of useful stuff with the language.
I think there is definitely gaslighting in the other direction too, which is what you're highlighting: lots of people know from experience that go is a super useful language, but then people come of as saying "your experience does not exist, it is not a useful language because of these big design flaws".
I suspect you’re talking about something I’ve seen, which is people will say that your experience in other languages isn’t directly applicable to writing code in Go because it’s a different language and ecosystem.
This seems like something newcomers need to hear sometimes, and it’s also somewhat irritating if in your case it doesn’t seem like that kind of issue. When generic advice for beginners gets misdirected then it can seem insulting, but I usually just try to remember that they don’t know me and beginner mistakes are worth checking for. I can choose not to be insulted.
Getting people to actually hear what you’re saying when they’re pattern-matching on common questions can be difficult. I think we should still assume good faith, though, and “gaslighting” implies malice.
Your more nuanced version of the argument is also wrong though. Experience from other languages is always applicable. Even for beginners, it is obvious and correct to ask "wouldn't we save a lot of repetition if we could write a generic version of this function instead of tediously rewriting the same thing for a bunch of types?". The correct and very reasonable response is "yes, but the designers of this language chose not to support that functionality in an effort to maintain simplicity". The right answer is not "no, this language is unique such that that technique is not applicable to it". That's gaslighting, and much worse when directed toward beginners who will just conclude that they must be wrong, when they aren't.
I agree with your last paragraph in general, but in this case that's not what was going on. Saying that the language would not actually benefit from polymorphism was not true, it was just a rationalization for leaving out a useful feature. It's the difference between "yeah that would be useful but we don't think the complexity it brings is worth it" vs. "that is not useful in this language for reasons you aren't wise enough to understand".
Although I like it being used for systems programming stuff like TinyGo on embedded or F-Secure TamaGo unikernel, that is hardly taking over the domain where C and C++ still rule, and will keep to do so for decades to come, despite the increasing usage of Rust on the domain.
I'm curious what you define as systems programming, because Go certainly isn't a systems programming language by the classic definition.
Go has a complicated runtime and GC. It's really not in the same category as C/C++/Rust, but more like Java/C# , just without a JIT.
The only domain where Go is a go-to language is the Kubernetes ecosystem. It's also decently popular for networking heavy applications / microservices / server applications, because the runtime is well suited for those domains.
Golang having GC and a heavy runtime make it unsuitable for systems programming IMO.
https://www.packtpub.com/product/creative-diy-microcontrolle...
The C# one seems to be further along though due to having supported the Mono/Xamarin AOT runtime for a long time and is really easy to setup, it’s just a package reference in your .csproj. It also has the benefit of coming with a modern usable language.
For example, there is a bug in go’s built-in HTML templating, that it misrepresent javascript backticks.
If you do
<script> var string = `http://google.com` </script>
in HTML template, it will interpret // as a comment and return
<script>var string = `http:</script>
This is now really hard to fix; it means either rewriting the JS parser from scratch, but that is a giant change (currently the JS parser is really simple, backticks are hard to do properly without reimplementing all from scratch); the more reasonable choice would be to just ban backticks in HTML templates, but that would break backward compat.
So there is basically an unfixable bug sitting in go html templates.
urllib/urllib2 in Python is one example. There are others; it's not really unprecedented.
Given script tags are allowed, this seems like the least reasonable choice. What other arbitrary JS features should be disallowed because the parser isn’t spec compliant?
Only if you want it to change. Our codebase is plain JS/TS, we don't use many recent features, and it's fine.
Looking forward to the day, actually.
I guess from the standpoint of a language designer it makes life a bit easier not to do anything and just cherry pick the winners of the various generic attempts that the community will create but as an app. developer I find this disturbing. Also I question the rush to a release all of a sudden. Generics took years to come to fruition. Another year for a decent stdlib won't really hurt anyone and for those that really really want it they can enable it through build constraints.