So for example, maybe you'd want to write a Map function for the Optional type in this article, which returns None if the option is None, or calls a given function with the value of the Optional otherwise.
You'd probably write it like this:
func (o Option[T]) Map[U any](f func(a T) U) Option[U] { ... }
But that doesn't work: "option/option.go:73:25: methods cannot have type parameters"The type inference is also a bit limited, e.g: let's say you have a None method:
func None[T any]() Option[T] { ... }
And you call it somewhere like: func someFunction() option.Option[int] {
if (!xyz) {
return option.None()
}
// ...
}
it isn't able to infer the type, so you have to instead (in this case) write option.None[int]().Generics is a super cool addition anyway though.
Edit: I just found https://go.googlesource.com/proposal/+/refs/heads/master/des... which has some details on why method type parameters aren't possible.
While you can't do this:
type Option[T any] struct{}
func (o Option[T]) Map[U any](f func(a T) U) Option[U] { ... }
You can do this: type Option[T,U any] struct{}
func (o Option[T,U]) Map(f func(a T) U) Option[U] { ... }
So this is not quite as restricting as it seems. Though it is still likely to be annoying. Runnable example: https://gotipplay.golang.org/p/2w2y1KEjXVEI love GO and it's simplicity and yes I do want generics, but is it just me or is this reading much much harder now ? It reminds me of those ugly voidfuncptr signatures of C and C++ :(
Maybe my eyes should just get used to it but I do feel a little my simple Go now reads not as easy. YMMV
This is totally opinionated, but I better not see code like this on a review. Is there a way to make it a bit more readable and a bit less like Perl?
Mind you, I wouldn’t mind colons and arrows as separators which I think make it easier to read; here’s what it’d look like in a somewhat more Rust-like syntax:
fn Map<U: any>(self: Option<T>, f: fn(a: T) -> U) -> Option<U> { ... }
I would also note that signatures like these are mostly found in foundational types; they take a bit more effort and practice to write, but you don’t often have to do so; and have the outcome that the API is more pleasant to use—no more interface objects and downcasting everywhere, in Go terms.The syntax in other languages with generics (C#, Swift, Java, and even c++) for this construct is easy to read. And obviously there’s always Haskell where you often don’t need explicit type annotations at all :D
public class JavaOption<T> {
public <U> JavaOption<U> map(Function<T, U> func) {
//todo
}
}
Kotlin might be a closer match in semantics if I use an extension function: fun <T, U> Optional<T>.map(func: (T) -> U): Optional<U> {
// todo
}At the max, one can make a couple of type-aliases for a bit more legibility, but that's all one can squeeze.
type Option[T any] *T
nil is None
opt == nil instead of IsNone()
func Some[T any](t T) Option[T] { return &t }
*opt instead of opt.Get()
option.Map(opt, func(x int) double { return double(x) }) for the monadic behavior
I wish there was type inference for function arguments, so that you could write func(x) { return double(x) }. Maybe in a couple of years the Go team could be convinced.i haven't written go in a long time (generics would/could get me to go back to it) but are you saying that functions can't be generic? or is members here vernacular for class (struct?) associated functions? i thought those were called "receivers", which you mention further down. so it looks to me like you're saying that functions can't be generic. to which i ask: wtf is the point of generics when functions can't be generic...?
// This is a function, you can use type parameters here:
func Foo[T any](g T) { ... }
type bar struct {}
// This is a method, you can't use type parameters here:
func (b bar) Foo[T any](g T) { ... }
In the second case, "Foo" is a method which has a "bar" instance as a receiver.I've edited my original post to make it a bit clearer.
Do you think Generics will be an overall win for the Go language, or will they be overused / end up making code harder to read / harder reason about?
I kind of hate looking at Go code containing generics. What previously was elegant and easy on the eyes is now annoying and muddled by the additional layer of abstraction. I'm saying this with sadness, as someone who fell in love with Go back in 2012 and still writes it at least weekly.
Is it a better bet to move on and go full Rust, rather than bother with wherever the goggle golang train is headed?
p.s. Even though code generation is [also] annoying and perhaps not ideal, I've rarely needed to use it and kind of liked that it was inconvenient - forcing me to think about problems differently. Certainly some problems will benefit from the addition of generics, but is it really enough to justify the added complexity? I wonder if this is a case of tragedy due to vocal minority.
p.p.s. Generics in other languages like Java or Scala seem fine, as they are "kitchen sink"-style "all things to all people" languages. Such behemoths are nearly always clunkier and less easy to read than pre-generics Golang.
Ecosystem aside, with Rust you'll get far better language design (generics fully integrated with everything (stdlib, consts, all libraries), sane error handling, sane dependency management, no "any" type, editions, and more. More complexity too though.
> Certainly some problems will benefit from the addition of generics, but is it really enough to justify the added complexity?
Coming from languages that have them, it's just hard to take Golang seriously, where every library either ditches type safety (more runtime errors I wouldn't have with other language), or forces you to copy-paste code just because you need support for some new type (more boilerplate to maintain == more errors). Or reinvents generics with code generation and aboriginal characters.
Once you start using generics, they really aren't complex.
Some people judge the language on their ability to get work done with it.
I get generics, but they really don't come up in daily use with the tasks where I use Go. Yes, it would be nice for writing some libraries but you're going through a laundry list of Rust features which don't hamper my ability to get work done at all.
I even like Rust, but if I'm going to write a worker that will read from a queue, do a transformation and write to a few more queues/services upon completion, Go just works and the turn around time is far better than Rust. It's like was Perl was for Unix, but for the cloud instead.
Tell me you’ve never used Go without telling me you’ve never used Go.
Here's my $0.02 from a java background. There will be cases where someone overuses generics. That happen pretty much every time a new language feature lands in any language.
However, my expectation is that like java, you will see Go generics in general practice only come up when dealing with things like collections. Once the community settles and stops trying to use generics as a meta-programming language, they will become pretty boring (which is what you want).
From a readability standpoint, IMO, generics in Java don't really have a negative impact on readability. Sure, you'll get the random `Map<Map<String, Object>, Object>`... but most people see that as the anti-pattern it is.
In short, I'm guessing they'll be a win.
It's that "correct by accident" part that's hard to have both safe and performant without generics.
I'm pretty disappointed to see generics introduced into the language and every example I've seen feels completely unreadable to me compared to pre-generics implementations.
To be clear, it has never been the case that the Golang authors were 100% against generics. It has always been their position that the implementation needed to be good enough to make the trade-offs worthwhile. I just don't think they chose the right trade-offs.
That's pretty surprising to me. Have you never had to implement marshalers for unknown types and such? I have had to implement things like json.Marshal and json.Unmarshal for different encodings dozens of times in my Go tenure. I have had to use reflection a lot. I have had to deserialize into map[string]interface{} to handle ambiguous situations at runtime a lot. Have you never even had to wrap or build your own Printf equivalents that accept interface{}? No loggers? No custom containers? None of that which operates on unknown types?
I see use of interface{} all over the vast majority of Go projects. I think your experience may be atypical.
Funny you say that because for me that's the one use case I have for generics.
(edit: why the hell am I getting downvoted for posting a fact? I wasn't offensive, argumentative, etc. Just citing one example I've run into where generics would help me personally)
Perhaps a Go 2 if they ever get there could be a generic std lib rewrite, largely transparent to the end user, with some minor incompatibilities allowed and lots of stuff rewritten behind the scenes. They could remove a few ugly corners in the stdlib naming specific types by using generics, add a few more container types perhaps, deprecate some old stuff and move it out.
Here's a fun one I stumbled on: How do you implement a PUT endpoint where a missing JSON value is treated different than a null JSON value? This ends up being very difficult and requires a boilerplate wrapper type for every single type you might accept. It's even worse when you start accepting slices or maps, or slices of maps...
These are areas where generics will help me a lot.
> How do I disable static typing for this statically typed language?
I have sympathy for your struggles -- I've been there. But fundamentally this always ends up being a problem of putting a square peg into a round hole.
I don't expect most folks to agree with this take, but I have the utmost faith it'll age well.
If you don't believe me now, set a reminder for ten years and see how we feel about JSON and GraphQL.
Tbf that’s a pain in the ass everywhere unless you’re reifying it as a map (so manipulating a json dom).
Iirc in rust the “complete” way to do this for a struct (as opposed to a map) with serde require two options and a bespoke deserializer.
If you don't like generics you shouldn't use Rust. You can't escape them in Rust. The designers repeated the mistake of C++ and made it a language feature kitchen sink. It's an unholy mess. You'll find yourself constantly fighting the borrow checker. Type signatures are littered with lifetime annotations. The type system is Turing complete because they didn't analyze it before implementing it. Go's generics were formally validated [1]. Rust's compile time is slow, and the 'async' story is sad. Async functions are colored and infect everything.
In the beginning, yes, this is true. But most people learn within a month or two which design patterns lead to problems with the borrow checker and which work smoothly, and often this knowledge translates to good design in languages like C and C++ as well.
If you're fighting the borrow checker in Rust, you'd probably have been fighting segfaults and use-after-free in C / C++. I'd rather spend 30 minutes fighting the borrow checker than spend 4 hours digging around in Valgrind.
> Type signatures are littered with lifetime annotations.
You cannot avoid the concept of lifetimes, without a garbage collector. If you don't want garbage collection, you have to deal with them.
Having explicit lifetime annotations in the code is _vastly_ better than trying to track the lifetimes in your head from scratch every time.
Personally I would discourage overuse of generics in an application codebase as I’d discourage overuse of interfaces, concurrency or channels - they have their place in certain areas (for generics e.g. collections, orms - mostly in library code) but most of the time simply aren’t required.
I don't want to read through a heavily templated, generic code base when concrete, simple types will do. Go is so easy to read and reason about that I'm genuinely afraid of any change which could affect that.
The best advertisement for avoiding generics in Go despite their availability is the fact that they won't appear in the standard library for some time, and that the language maintainers believe it will take years to understand how to use them appropriately.
On the positive side, now Either/Try-like composable types are possible. So even if they insist on not having exceptions they could clean up error processing.
I second that sentiment, but I would have to look into the specifics. Generics ala Java aren't really that attractive when Rust mainstreams ML-style polymorphism with Haskell-style overloading.
First, the Go community mainly comprises people who love simplicity and got accustomed to it. I imagine most people who want to go overboard with generics will stay with languages that let them go way more overboard.
Second, and more importantly, there's no method parameterization, which saves us from monadland.
IMHO Go generics are simple and useful, particularly for container libraries. They are fairly readable, unlike template programming and macros in other languages. Together with the any type alias for interface{} they will make code more readable.
I do hope that Go stays at version 1, though, or that it at least takes a long time to add new substantial features and get version 2. Slow change is one of the many advantages of Go and I'd rather see them improve the compiler in hidden ways.
Will it be nice with generics for carefully crafted and cherry-picked use-cases? Absolutely.
Will it make the average large codebase less readable after a dozen coders have been doing their own cleverness with generics 3-5 years down the line? In my experience, most definitely.
Of those two, I'm much more afraid of working with the latter than missing out on the former.
It sometimes feel like we as devs have a habit of optimizing for the individual dev's convenience (not to mention what's fun) rather than for the collective effort's best interest - like the long-term maintainability/coherency of the code base. To some extent I assume that's just being human, but in other professions I think it would be seen as a bit lazy and thus more stigmatized.
A bunch of people will write libraries that utilize generics in some way that's not very orthogonal to the rest of the Go ecosystem. They'll put these libraries out as quickly as possible, because they want a first-mover advantage in picking up adoption. A handful of people will take their time and learn how to utilize generics in a way that is native to Go. Eventually those people come out of the woodwork, and it turns out that utilizing generics in Go looks different than it looked in some other language, so a lot of the early assumptions about how to use them were wrong. A bunch of those early libraries turned out to be badly designed once actually deployed into the real world, so they fade out of use as their problem domains get written by newer libraries that utilize generics in a manner more orthogonal to the rest of the Go ecosystem. A few of those libraries picked up significant market share, and maybe a startup has shipped stuff that generates revenue with those libraries, so they fund those projects, which continue to exist as a result of pure inertia. So now you have some big libraries written in like ... March of 2022, which are just bad, but people keep promoting them because they have a vested interest in doing so, but the ecosystem at large moves on, and by late 2022, generics will fit in very nicely with Go and not complicate things in an unnatural way. I'm wary of what's coming in the short term, but optimistic of what's coming in the long term as a result of this change.
I'd go for Rust tbh. I think it's a much more coherent language.
I like Go because of the tooling and simplicity of the language, it's easy to learn and explain.
Rust has the borrow checker has a corner stone. The concept mutability and references is easier to understand if you are coming from the c/c++ side of things
While generics moves Go in the direction of rocket science, it feels like this is solving a problem I always have.
If you want to unnecessarily encumber your mind with trivial but tedious memory management puzzles, then choose Rust.
> I’m not sure that most Go developers will be using generics daily, but it’s nice to know that they exist if we need them.
Most people won't need generics in Go, and I hope people don't force them into their code where an interface would do just fine. I'm a big fan of good type systems like Rust, but Go doesn't need all that power all over the place. I think generics are a good feature for Go, but I really hope they don't get overused in places that would currently use an interface.
I doubt more complex features than this will be added, considering how long it took to get generics.
Rust is a very different language from Go, though it can do similar things. If you have people willing to learn it, you can certainly try, though you might find the ecosystem lacking.
If they will make code harder to read, that's up to syntax. I don't know how all that will look up on the end, but it should be reasonably easy to just write an example.
do you mean templates?
i was under the impression that c++ generics == templates, but after a google search found out that c++ has both (at least according to microsoft), not surprised
https://docs.microsoft.com/en-us/cpp/extensions/generics-and...
Go is fairly dominant in some areas and Rust is not really a realistic replacement today. Generics will not change that.
Perhaps that will change as you get more used to it?
And for code you consume (e.g. using a library) Generics will make your life easier and safer.
Pretty much any mainstream language that supports generics is shit to read. (And I love to read code.)
You're just getting old is all.
What was once familiar now isn't ... how annoying.
If that worries you, don't: we're all walking down that path.
Give it a couple of decades and everything in your life will feel like that.
And then the author goes to admit that they had written a whole library with the kludge that is textual code generation "to support both primitive and custom types".
> My first response when the plan to add generics was announced was “meh”. In my 5+ years working in Go, I can probably count on one hand the number of times that I felt like I really needed generics. Most of the code I write in my day job is very specific to the domain and doesn’t fit the use case that generics aim to fill.
to
> I love that I was able to delete 95% of my code because of generics.
An argument against generics was that people found it hard to find examples that were 'real' where generics would be beneficial, and so because it was rarely needed the question of whether the language should be drastically bodged/ruined/adjusted for this feature was called into question.
In retrospect you had a self-selecting population of people who loved Go and presumably didn't have much use for generics, whereas people who did presumably used something else.
I guess all we can learn from this is that human imagination is poor, and many of us need the thing in our hand to work out what we can do with it.
Same with people who unconditionally recommended WhatsApp not that long ago.
Or people like me who told everyone Google was still nice and a driving force for good until a few years ago ( yes, I still have some hope that they will change their ways and don't think others are much better but I am somewhat bitter and I don't give them the benefit of doubt anymore :-| )
type Option[T any] struct {
v T
isSet bool
}
func NewOption[T any](v T) Option[T] {
return Option[T]{
v: v,
isSet: true,
}
}
func (o Option[T]) Get() (v T) {
if !o.isSet {
return v
}
return o.v
}
func (o Option[T]) IsSet() bool { return o.isSet }
With this pattern you're able to use `Option` as a value without pointers. var o Option[int32]
o = NewOption(int32(1))
fmt.Println("value:", o.Get())
fmt.Println("is set:", o.IsSet())
Alternative separate `Get` and `IsSet` methods, is to combine them into one, similar to map look up pattern. func (o Option[T]) Get() (v T, isSet bool) {
if !o.isSet {
return v, false
}
return o.v, true
}
var o Options[int32]
v, ok := o.Get() // zero, false
o = NewOption(int32(1))
v, ok = o.Get() // 1, truePutting the end first, my rule of thumb for using generics in Go is: Don't go down the OOP road of over planning and programming with fancy type work. 99% of the time, the common Go programmer won't need to write any generics. Instead, just focus on actually solving the problem and manipulating the data like you would normally. If you encounter a place where code is repeated and complicated enough to be worth a new function, move it to one. If you find yourself repeating multiple functions but with different data types, turn that into one generic function.
Generics are an incredibly useful addition to the language that I'll almost never use. Really to be more precise, Go has had some generics this whole time: Maps, slices, arrays, and channels all have type parameters, and have covered the vast majority of my needs. There are a few times where I've wanted more general generics, though:
- The sort and heap packages are rough to use. You need to specify a bunch of nearly identical functions just to get them to work on any custom type. The generic versions (not coming in 1.8's standard library, iirc) will be much easier to use.
- Was writing an Entity-Component-System game for fun, and needed a weird custom container. Turned to code generation, and really that turned out to be necessary anyways because it did more than any (non-metaprogramming) generics could do.
- We had one very complicated multiple Go routine concurrent data structure that needed to be used for exactly 2 different types. Others were writing the code, and very afraid of using interface{}. This is despite there only being a handful of casts. In reality if they caused a bug, it would be found immediately. There's a strong hesitation around type safety dogma that isn't risky in practice. Still, generics would've been the preference here.
- I was parsing WASM files, and there's a common pattern for arrays where it encodes the length of the array, then that many objects in a row. It led to a lot of minor code repetition. Replacing that with a generic function that took a function to parse a single object, and returned the array of those objects was a nice, but relatively minor win.
On the other hand:
I've never really been bothered by having to do sets like map[int]struct{}. There was one case where I saw someone put set operations out into a different library. I eventually found to my dismay that the combination of how the set library was used, and how it was implemented caused a performance critical part of the code to be several orders of magnitude slower than it needed to be. Had this code been more idiomatically inlined, this flaw would have been more immediately obvious.
I really don't like seeing map/reduce/filter type functional programming coming into Go usage. This type of code tends to need more dramatic changes due to minor conceptual changes, more than direct procedural code does. Also like the set example, how you iterate and manipulate objects can have large performance implications that using such functions hides away.
I used Go for about six months and eventually abandoned it to pursue Rust, a decision I've been extremely satisfied with. The longer I used Go, the more I grew to hate it and error handling was one component of that.
Well over half of Go source code in practice is dealing with errors, and somehow the Go ecosystem has convinced themselves that "verbose" is the same as "explicit" when it doesn't need to be. The worst problem isn't that it's just a lot of excess code, it's that it makes all sorts of very simple and common programming tasks ridiculously unwieldy. The most obvious example is calling a fallible method, doing something to the result, and returning it (or the error). This is one single character in Rust but a minimum of four lines—with branching—of copy-pasted boilerplate in Go. Which isn't a lot in the abstract, but then you multiply that by hundreds of times and now I have read, lex, parse, and mentally discard the majority of pages of source code that's doing something that could be done in ten lines with a massive incerase of clarity in a more reasonable language.
You've probably "never seen" us because we felt very let down by the overpromise and underdelivery of go and we left.
After a while, I tried using the Goland IDE, and its static analysis tool found a dozen places where I wasn't handling errors correctly: I was calling functions that return errors (such as `io.ReadCloser.Close` or `http.ResponseWriter.Write`) without assigning their results to variables, so any errors produced by them would simply be ignored. My code was compiler-error-free, go-vet warning free, and still, I was shipping buggy code.
A few months later, I try using the golangci-lint suite of linters, and again, it found even more places where I wasn't handling errors correctly: I was assigning to `err` and then, later, re-assigning to `err` without checking if there was an error in between. My code was still compiler-error-free, go-vet warning free, and now IDE-warning free — and I was still shipping buggy code.
I don't see how anyone can see this as anything other than a big ugly wart on the face of the language. It's not because it's repetitive, it's because it's fragile. Even with code I was looking at and editing regularly, it was far too easy to get wrong. I'm going to continue using Go because it still fits my purposes well, but I'm only running it on my servers, so any mistakes I make are on my head, rather than on anybody else's.
I also don't think Go's design is really amenable to things like the Option and Result types people are writing — yes, I would never have had these problems in Rust, but code written using them in Go is clunky and looks out-of-place and doesn't feel like it's the right thing to write. I wouldn't ever use the `Optional` type in the article. But it's definitely not a solution in search of a problem. There's a huge problem.
a,b := w < x, y > (z)
There are two valid parsings of that if generics use <> and at parse time it isn't known which one to use. Either: a, b := [boolean expression w < x], [boolean expression y > (z)]
or a, b := [generic function w] [with parameters <x,y>] [applied to (z)]
https://groups.google.com/g/golang-nuts/c/7t-Q2vt60J8I almost feel I would be happy with generics in Go if Go made them illegal in anything but libraries (not allowed in package main, maybe? Or not allowed in a package unless it gets imported by another package?).
[]MyContainer[T] // slice of generic struct or interface
can/must be written as
[](MyContainer[T])
but ([]MyContainer)[T] isn't a valid use of generics anyways.
T int = 5
myarray[T]Gives me hope that P vs NP will be resolved in my lifetime too!!
Not really but getting to know Go is not on the list of my priorities.
Looked at examples. Many languages use angle brackets for generics and templates but in case of Go they had to do it their own way and use square brackets that most programmers would perceive as an array. Funny.