[HN Gopher] Trying Out Generics in Go
___________________________________________________________________
Trying Out Generics in Go
Author : bullcitydev
Score : 143 points
Date : 2021-12-16 17:41 UTC (5 hours ago)
(HTM) web link (markphelps.me)
(TXT) w3m dump (markphelps.me)
| Smaug123 wrote:
| I think you're making it easy on yourself by choosing "a library
| whose sole purpose is to stamp out the boilerplate needed to work
| around Go's lack of generic algebraic data types" to demonstrate
| how good generics are :P
| bullcitydev wrote:
| haha for sure. but still better than code generation IMO ;)
| xyst wrote:
| person deleted 95% of code, but what about performance? is it
| more or less the same as the non-generic implementation?
| amelius wrote:
| Side question: Are there any languages that transpile to Go?
| jaytaylor wrote:
| Honest question:
|
| 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.
| rackjack wrote:
| Every language feature gets misused in some way, but if it is
| overwhelmingly used to clarify and reduce code, I think it is a
| win. I believe generics will overwhelmingly be used to do this,
| so I think they are a win.
|
| 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.
| amelius wrote:
| > Is it a better bet to move on and go full Rust, rather than
| bother with wherever the goggle golang train is headed?
|
| If you want to unnecessarily encumber your mind with trivial
| but tedious memory management puzzles, then choose Rust.
| cloudfifty wrote:
| I'm not so sure it will be an overall win.
|
| 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.
| fiedzia wrote:
| > Is it a better bet to move on and go full Rust, rather than
| bother with wherever the goggle golang train is headed?
|
| 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.
| hpen wrote:
| Is Rust really a suitable replacement for Go? I mean I know
| they are both system languages but I feel they have different
| use cases completely.
| pizza234 wrote:
| I'm a Rust "fanboy" (if one wants to say so), and I still
| think that the use cases for Rust are just a minority in
| the landscape of modern languages (that is, where there is
| a choice).
|
| Rust has a mind-boggling overhead, for many reasons (and
| I'm not talking about the borrow checker, which I think one
| gets used to after some time), even if the language itself
| is consistent and ergonomic (within the intended
| constraints).
|
| To me, they have very different use cases - one will
| definitely know when Rust is required or it's an
| appropriate pick. For the rest, Go is fine. I think that
| those who put them on the same basket, haven't really used
| one of them.
|
| Regarding systems programming, my opinion is that they
| require the lack of a runtime (not just because of the
| performance, but also, for the framework(s) written with
| low-level primitives in mind), but this is arguable (in
| particular, there's no clearcut definition of what systems
| programming is).
| hpen wrote:
| I have only touched Rust & Go but I would only consider
| Rust where I would previously have used C or C++. The
| stuff that needs to be fast, low memory, and what not. I
| see Go as more of a Java. Good for backend systems. Also
| CLIs.
| fiedzia wrote:
| > they are both system languages
|
| Due to ability to work without stdlib, Rust is system
| language in a sense Go never will be, to the point Go
| authors withdrawn this definition.
|
| > Is Rust really a suitable replacement for Go?
|
| It depends on your requirements for the ecosystem. If you
| need compatibility with Go or Go libraries, than it's not a
| replacement.
|
| Libraries and support aside, any program written in Go can
| be written in Rust (and going back to nostdlib, many
| programs written in Rust cannot be written in Go). If you
| have required libraries, I'd say it can be written
| comparably quickly and easy. For example you can have web
| service returning hello world in 10 lines of code or so in
| either.
|
| They are popular in different circles, but that is mostly
| not related to technical abilities. For 99% of of
| applications, you could pick either one.
| [deleted]
| saghm wrote:
| Slight nit: Rust does have an `Any` type[0] (and has since at
| least 1.0). Unlike Go's `interface{}`/`Any` type though, it's
| actually type-safe; the only way to get a value from it is to
| try to downcast into a concrete type, which returns an Option
| and will always be `None` if it doesn't match the type.
|
| [0]: https://doc.rust-lang.org/std/any/trait.Any.html
| LanceH wrote:
| > Coming from languages that have them, it's just hard to
| take Golang seriously
|
| 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.
| pjmlp wrote:
| That comparison is a disowner for Perl, given its language
| capabilities.
| mseepgood wrote:
| It's a disowner for Go given that Perl's philosophy is
| "more is more" and Go's philosophy is the Wirthian "less
| is more".
| fiedzia wrote:
| > I get generics, but they really don't come up in daily
| use with the tasks where I use Go.
|
| I sort of understand this argument, but I can't really
| imagine defining queue as something else then <T>
| wait_for_item() -> T. I've been writing Python for to long
| to know that wait_for_item() -> any will backfire in
| production eventually and I don't want that. At some scale
| (both in code size and amount and scope of dependencies)
| those problems just become too serious and too common to
| not have language that deals with that. And Go is way too
| popular for people to limit its use only for the cases
| where it currently works.
| LanceH wrote:
| Define queue using generics, sure. It will be huge for
| people waiting packages.
|
| A specific queue is typically well defined and has a
| struct in/out.
|
| There are times when I've wanted arbitrarily nested JSON
| that doesn't map into structs very well, but it is
| uncommon enough.
| choeger wrote:
| > Some people judge the language on their ability to get
| work done with it.
|
| Really? Safety and correctness aren't relevant for you?
| Then why even bother with go instead of Python or a Lisp?
|
| For me it's crucial that a programming language contains
| tools that allow me to definitely rule out as many errors
| as possible. A powerful (and sound) typesystem does just
| that.
| fnord123 wrote:
| Maybe they don't want a target directory using 3gb from a
| clean build.
| cloudfifty wrote:
| It's already self-evident that Go manages that just fine.
| That's just weird dogma.
| ori_b wrote:
| How are you finding Idris? or are you more in the Agda
| camp?
|
| If you want to definitely rule out as many errors as
| possible, dependently typed languages are the state of
| the art, allowing you to write a sort function that will
| fail to compile if it returns a list that isn't sorted
| (eg,https://dafoster.net/articles/2015/02/27/proof-terms-
| in-idri..., or https://www.twanvl.nl/blog/agda/sorting).
|
| After all, if you can't even prove basic properties about
| your code from your language, like array accesses being
| within bounds, are you really using all possible tools to
| rule out errors at your disposal?
| choeger wrote:
| To be fair, I never had a chance to use Idris nor Agda. I
| don't know how capable I would be to encode the proofs in
| libraries nor client code. If it's usable, I am all for
| it.
|
| Otoh, I do know that languages like OCaml, Haskell, or
| Rust take the burden of trivial errors from my shoulders
| for neglible cost.
| grey-area wrote:
| Does Rust also have a feature that instills a burning
| desire to proselytise?
| choeger wrote:
| I like Rust mostly for its user-friendly tools. I dislike
| the compilation model. The type system I already liked
| even before Rust existed. So, not every String opinion on
| PL is down to Rust.
| AnimalMuppet wrote:
| > > Some people judge the language on their ability to
| get work done with it.
|
| > Really? Safety and correctness aren't relevant for you?
| Then why even bother with go instead of Python or a Lisp?
|
| A charitable reading of the GP would be that "safety and
| correctness" would be included in "getting work done", in
| amounts that are appropriate to the work in question.
| Your interpretation is... less than charitable.
| LanceH wrote:
| Exactly. Go is safe enough. Correctness for most programs
| is more about making sure it solves the problem
| correctly, not merely that it guarantees no errors if it
| runs. That feels like a modern version of "It compiles,
| I'm done."
| eweise wrote:
| I use Go every day and yeah you can your work done with it
| but not quickly due to typing all the boilerplate.
| cube2222 wrote:
| Agreed.
|
| I wrote a bunch of Rust, Scala, Haskell, but I still
| greatly prefer Go, even without generics.
|
| I am very happy with the generic container libraries I'll
| get with generics, but I hope people won't try to be too
| clever (as they usually do). So far, Go is a language that
| just rules out a lot of bikeshedding, which I very much
| appreciate. We'll see how that evolves.
|
| I do think premature or wrong abstractions are a much
| bigger and widespread problem than a lack of abstraction.
|
| Also, I really like Go's error handling, it results in
| error messages in Go projects usually being top-notch
| (because of explicit handling and wrapping which includes
| relevant context and human-readable messages).
| garethrowlands wrote:
| Premature or wrong anything is clearly bad. But is
| parameterisation a premature or wrong abstraction? I'd
| say that parameterisation - even in the type language -
| is the original proven abstraction.
| cube2222 wrote:
| Yes, in my opinion oftentimes it is.
|
| There is a reason why even in mathematics people like to
| operate on concrete examples to get an intuition. For
| many, concrete is much easier to understand than
| abstract.
|
| That's the less important point. The more important point
| is that making your code generic often involves more
| trickery which makes the code more complex, even if you
| only use the code once or twice - so that's just effort
| wasted.
|
| The fact that parameterization is a proven abstraction
| doesn't mean it's good everywhere. Same as I don't agree
| with the "Clean Code" way of creating a myriad of 4 line
| functions.
|
| Yes, good programmers won't make these mistakes, you can
| totally handle them. But when arriving at legacy code or
| open-source projects I greatly prefer to find under-
| abstraction rather than over-abstraction.
|
| To be clear, I'm not against generics, I just agree with
| the parent of my original message. I'm worried people
| will overuse them and I don't want a whole laundry list
| of Rust features in Go. I'm very happy about libraries
| with type-safe generic B-Trees.
| Jtsummers wrote:
| I think this is why I always have trouble understanding
| arguments against generics (in the general case at
| least). Parameters are added to functions/methods in
| order to make them more generic/flexible, as a rule. To
| move something that may have been hardcoded into
| something that can now be configured:
| get_data() { filename=default ... } =becomes=>
| get_data(filename=default) { ... }
|
| When the type either does not matter, but the concrete
| instance records it, or the type makes sense to be
| configurable, you want generics. As a silly example (but
| short enough to fit into a comment block):
| fn nth(seq: [int], n: int) -> int
|
| Now you have to make a new _nth_ for every single
| sequence type, even though it has no bearings on the
| actual operation. Or you make it generic:
| fn nth<T>(seq: [T], n: int) -> T
|
| That's a trivia case, yes. But if anyone has ever worked
| on a complex code base there are plenty of situations
| like this that turn up, at least in my experience. Sure,
| I almost always start off with concrete instances with a
| fixed type, but as soon as it becomes apparent that the
| type itself is irrelevant and I have a couple use-cases
| with different types, why _not_ make it generic and be
| done with it? Like, would you _really_ have more than one
| version of that _get_data_ function running around, one
| for every conceivable filename? That would be obscene.
| Why would you do the same with types?
| krylon wrote:
| I'm with you. Go has its problems, and I am aware of
| them, but it is just _so_ compatible with the way my
| brain works, it is amazing.
|
| I remember listening to a podcast about C++, and the
| guest explained how after working with C++ for about five
| years, they still encountered aspects of the language
| that surprised them on a regular basis (to be fair,
| though, that was before C++11). To me, Go just clicks in
| way few languages did.
| kubb wrote:
| There is a market for a language like Rust but with garbage
| collection and reflection. There is OCaml, but it's not for
| the modern developer. Go with generics is the closest thing
| to that which is getting some use.
| jpgvm wrote:
| I think so too but I think the languages that fit the bill
| are Kotlin and Swift. Both modern syntax, great generics
| w/GC and ARC respectively.
|
| Go w/generics falls very short IMO, expressibility, type
| safety and poor null handling all rule it out as a
| reasonable stand-in for Rust.
| wbl wrote:
| Why not Ocaml?
| cb321 wrote:
| This more|less also describes Nim. The recent automatic
| memory management ARC/ORC alternative is not really even
| what most people think of as "garbage collection". Its
| macros give you full AST accept & re-emit powers and it's
| had generics since before Go existed. I realize it probably
| does not score highly on "gets used", but it deserves more
| attention.
| echelon wrote:
| Swift is pretty much that language. It doesn't have the
| imitable crate ecosystem or tooling, though.
| ReleaseCandidat wrote:
| > There is a market for a language like Rust but with
| garbage collection and reflection.
|
| That's why languages like JS/TS, Haskell, Elixir, OCaml
| (which is way more modern than Go), ... exist and are used.
| cogman10 wrote:
| > 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?
|
| 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.
| fizx wrote:
| I use Go because it compiles quickly, the GC is reasonable, the
| ecosystem is good enough, and it's not rocket science
| (Scala/Rust/etc).
|
| While generics moves Go in the direction of rocket science, it
| feels like this is solving a problem I always have.
| crehn wrote:
| I'm concerned about two things (that I hope I'm wrong about):
| (1) developers will overuse generics and use poor types or
| `any` where not necessary, and (2) this marks the beginning of
| Go's convergence to yet another language with a million choices
| for abstractions and a million ways to misuse its features.
| grey-area wrote:
| I think they did this really quite elegantly by extending the
| interfaces abstraction.
|
| 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.
| ur-whale wrote:
| > What previously was elegant and easy on the eyes is now
| annoying and muddled by the additional layer of abstraction
|
| 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.
| mseepgood wrote:
| > Is it a better bet to move on and go full Rust
|
| 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.
|
| [1] https://arxiv.org/pdf/2005.11710.pdf
| [deleted]
| opnitro wrote:
| A type system being turing complete really isn't a problem.
| You bound the recursion depth in practice, and the chance of
| a real world programming hitting that limit is minuscule.
| Lot's of other languages have turing complete type systems,
| subtyping and typeclasses lead towards it.
| mseepgood wrote:
| Sure, if you regularly want to increase your
| #![recursion_limit] or #![type_length_limit] for the next
| generation of type bloat.
| socialdemocrat wrote:
| Hmmm I guess it is all in the eye of the beholder. If you
| are the kind of person who thinks C++ went wrong with its
| template system, then you might find issue with any
| language emulating C++ failures.
|
| If you think C++ is a beautiful well deigned language, then
| I am sure you will not have issues with Rust either.
| opnitro wrote:
| Java & Haskell both have turing complete type systems as
| well. I'm not a particular fan of C++'s type system,
| doesn't mean have metaprogramming features is bad.
| dralley wrote:
| >You'll find yourself constantly fighting the borrow checker.
|
| 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.
| lawl wrote:
| > If you're fighting the borrow checker in Rust, you'd
| probably have been fighting segfaults and use-after-free in
| C / C++.
|
| That is in my ( admittedly limited) experience just not
| true. There's plenty of things that are perfectly safe that
| the borrow checker just doesn't understand.
|
| The borrow checker can prove that a subset of things is
| safe. But the borrow checker being unable to prove
| something doesn't mean it's not safe.
| giovannibajo1 wrote:
| This, one thousands times.
|
| The borrow checker forces you to write in the very narrow
| subset of code paradigms it can understand. When it fails
| to compile, it doesn't mean it's wrong: it means that it
| can't prove that it's correct, which is a completely
| different statement.
| socialdemocrat wrote:
| Ah yeah, I am not sure everybody know me what "colored" means
| but I remember when comparing a C# solution using async with
| a Go solution using channels and Go routines I finally
| understood why people keep raving about concurrency in Go. It
| composed very nicely while any language following the popular
| async/await approach turns into a total mess. I guess
| async/await looked good years ago because we compared it with
| managing POSIX threads manually. That sucked.
| curun1r wrote:
| It's worth noting that Rust's Async functionality starts
| with very different priorities from Go's goroutines. Rust,
| as a systems language, made lack of overhead (allocations,
| etc) the highest priority. It's part of Rust's overall
| zero-cost abstractions ethos. Goroutines are just never
| going to be zero cost. Go chose to prioritize the interface
| to the programmer, which is much more a part of Go's ethos
| around being simple.
|
| There's nothing wrong with either approach, they just have
| different trade-offs because their goals are different.
| Rust's approach will sometimes push complexity onto the
| programmer to handle. But it can be made to perform better
| and more predictably than the Go equivalent. This might not
| matter if you're not pushing the performance envelope, but
| if you are, Rust makes that possible in a way that Go
| simply doesn't. You'd never want to write code using
| goroutines for an embedded device with limited CPU/memory,
| but Rust's async is already proving useful for these sorts
| of projects.
|
| However if you can tolerate the performance overhead that
| Go imposes, giving the programmer a simpler mental model
| can easily be worthwhile. Technology is all about trade-
| offs and you have to choose the right tool for the job.
| djur wrote:
| Async/await was intentionally chosen by a lot of languages
| well after Go had become popular. Rust once had Go-style
| concurrency and abandoned it in favor of its current model.
| gpderetta wrote:
| Paraphrasing Stroustrup, there are only two types of
| languages: those (that end up) with a Turing complete type
| system and those that nobody use.
| ppeetteerr wrote:
| Not a Go programmer but I can say that Generics were important
| for me to consider adoption of Go. Now that they are present,
| it's a legitimate language. Whether they make code messy really
| depends on how they are being used.
| choeger wrote:
| > Now that they are present, it's a legitimate language.
|
| 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.
| spion wrote:
| What about `if err return err` everywhere?
| nynx wrote:
| I'm certainly not a fan of Go, but I suspect generics will
| result in the language and ecosystem getting better. I'm not
| personally a fan of the go generics syntax because I find
| braces and parentheses to be visually similar, but I'm sure
| that would go away if I used it more.
|
| I'd go for Rust tbh. I think it's a much more coherent
| language.
| spinny wrote:
| Wrote Go code daily for quite some time, also tried Rust.
|
| 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
| marcosdumay wrote:
| Hum, I've never seen generics making code harder to reason
| about in any language, except, of course for C++, where they
| are hacked over text.
|
| 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.
| spinny wrote:
| > Hum, I've never seen generics making code harder to reason
| about in any language, except, of course for C++, where they
| are hacked over text.
|
| 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...
| marcosdumay wrote:
| Yes, generics in C++ are created with templates.
| tsimionescu wrote:
| C++/CLI is C++ compiled to NET CLR IL, so it has all the
| features of the regular C++ compiler (templates) AND all
| the features of the CLR (generics).
|
| It is an extremely niche language, extremely rarely used
| even in the .NET ecosystem, except sometimes as glue code.
|
| Normal C++, including MSVC C++, has only templates.
| pjmlp wrote:
| It is definitely nicer than trying to correctly get
| P/Invoke declarations or debug COM marshaling issues.
|
| This is the kind of tooling that makes me still reach out
| for C++ when going outside managed languages.
| tsimionescu wrote:
| My experience was that P/Invoke is much easier, but of
| course YMMV. COM I never played around with.
|
| I also found a pretty ugly bug in the C++/CLR compiler -
| if you used in-place initialization for an array
| (something like auto arr = Object[]{obj1}), it would
| allocate an array of length 0xC0FFEE and set the elements
| you specified. They acknowledged the bug but said they
| will only fix it in a future version of the language.
|
| This told me all I needed to know about how popular it
| actually was.
| johnmaguire wrote:
| As someone who's been writing Go full-time for only about 8
| months now, I've repeatedly been frustrated with the lack of
| generics while building web APIs. For example, the Go GraphQL
| ecosystem is a bit of a disaster full of type unsafe code and
| use of reflection or code generation to support simple things
| like "a resolver that returns FooResult" vs. "a resolver that
| returns BarResult."
|
| 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.
| masklinn wrote:
| > How do you implement a PUT endpoint where a missing JSON
| value is treated different than a null JSON value?
|
| 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.
| johnmaguire wrote:
| It's much easier to do in dynamically typed languages or by
| using maps - agreed. Unfortunately we have other design
| decisions that force us into using structs for
| deserialization on this endpoint (part of our validation
| strategy.)
|
| The Go answer is a struct that contains an IsDefined
| boolean (i.e. your first option), and a pointer-to-value
| (i.e. your second option.)
|
| This is fine if you need it, but having to write this same
| logic over and over again for every type gets old...
| especially if your validators are tied to your types (i.e.
| a type per field.)
| zemo wrote:
| having played with generics over the past few weeks: my guess
| is that there will be a period of widespread misuse, the first
| six months or so after 1.18 is released. So... between February
| and August of next year I'm expecting a lot of the Go discourse
| to be characterized by a very poor signal to noise ratio. I
| think there's virtually no chance that Go 1.18 will come out
| and people will use generics well; people will use generics
| poorly before they use them well.
|
| 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.
| [deleted]
| horsemans wrote:
| I have never needed generics in Go, and I've probably been
| using it since 2017. I've never even once had to resort to any
| interface{} trickery to express what I want, and I've written
| Go programs for Fortune 50 companies, as well as complex
| personal projects such as AST parsers/code generators.
|
| 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.
| qaq wrote:
| never needed min/max ?
| kodablah wrote:
| > I've never even once had to resort to any interface{}
| trickery to express what I want, and I've written Go programs
| for Fortune 50 companies, as well as complex personal
| projects such as AST parsers/code generators.
|
| 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.
| cube2222 wrote:
| I share your worries, but I think it will be just fine. There
| are two reasons I have for that:
|
| 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.
| jjice wrote:
| I hope that this quote from the article is true:
|
| > 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.
| 13415 wrote:
| In my opinion, there are plenty of good alternatives to Go such
| as Nim, or Python if performance allows, and even languages
| like Zig, V, CommonLisp, and D depending on the use case. I
| don't get why people keep mentioning Rust in threads about Go.
| It's perhaps a replacement for C, C++ and Ada - though for the
| latter only if you're okay with switching from self-documenting
| code to unreadable gibberish. Rust's philosophy is pretty much
| the opposite of that of Go, and it is neither designed nor
| suitable as a high productivity, easy to use language.
|
| 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.
| spinny wrote:
| While generics are definitely useful to implement container
| types, it seems that the Go design seems to prevent that. It
| seems to me that generics get overused when the language allows
| you operator overloading for example. Go is a "one way to do
| thing X" language, it doesn't give much room to write
| "creative" code
| FpUser wrote:
| >"In case you've been living under a rock ..."
|
| 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.
| bullcitydev wrote:
| Author here. I just removed the previous section I had around
| using build tags because I realized I was using `// +build 1.18`
| instead of the correct `// +build go1.18`. Oops.
| grey-area wrote:
| Shame build tags aren't part of the language with a proper
| syntax check instead of magic comments.
| giovannibajo1 wrote:
| Can you show me an example of a language that does syntax
| checking of build-system related directives at compile time?
| travisd wrote:
| FYI: Looks like code blocks are unreadable on mobile (probably
| a overflow: hidden CSS rule somewhere that truncates lines and
| doesn't let you scroll).
| bullcitydev wrote:
| Just fixed it. Thanks again!
| bullcitydev wrote:
| thanks! I'll check it out!
| geoka9 wrote:
| It could be my personal negative experience with maintaining code
| that overuses generics in other languages, but I have
| reservations about this feature. I almost never need them, but on
| the other hand I don't feel too good about having to repeat
| myself when writing library packages.
|
| I 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?).
| FpUser wrote:
| Yes generics / templates are mostly useful for general
| libraries. But if you afraid of programmers doing stupid things
| just for the fuck of it it is better not to hire such
| programmers, warn them if they're juniors or just hit them with
| the bat on code review.
| geoka9 wrote:
| That could be said about any language though? Yet here we
| are, arguing whether generics are good for Go, instead of
| just using Java.
| random314 wrote:
| This is a phenomenal achievement, that I didn't expect to see in
| my lifetime!
|
| Gives me hope that P vs NP will be resolved in my lifetime too!!
| hmmdar wrote:
| Another way to do `Option` without pointers could be similar to
| the following with a struct with two members.
| 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, true
| 1_player wrote:
| I don't understand your example, `isSet` is always true and can
| never be false. Missed something?
| hmmdar wrote:
| The only time `IsSet` would be false is when `NewOption` was
| not used to initialize the value.
|
| e.g. var o Option[int32]
|
| or could have `None` helper func None[T
| any]() Option[T] { return Option[T]{} } o :=
| None[int32]()
| iambvk wrote:
| I agree with `Get` returning `(T, bool)` I don't see why one
| would want to return an `error`.
| dsnr wrote:
| That's a welcome feature even though I don't like the syntax. But
| Go could become the perfect language if they just fixed error
| handling and a couple small annoying quirks.
| Jemaclus wrote:
| Can you elaborate on "fix[ing] error handling" and what some of
| those small annoying quirks might be? I've got my own
| annoyances, but am always interested in what other people
| think.
| fourseventy wrote:
| I like the go error handling
| dewey wrote:
| I've never seen a person who writes Go for more than a few
| weeks complain about the error handling. I certainly don't mind
| it myself. Is it really a problem people have or just somewhat
| of a meme at this point?
| stouset wrote:
| Hi, I'm one of these people.
|
| 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_.
| mseepgood wrote:
| > This is one single character
|
| So you're bubbling up the error without annotating it...
| great
| stouset wrote:
| There's so much wrong in just this once sentence it's
| going to take a surprising amount of text to cover it
| all.
|
| First, if you want to add extra annotations or scope to
| the error, you can actually do so--and trivially--while
| still using that single `?`. Widely-used error crates
| like `thiserror` allow you to specify that (for example)
| an I/O error will be automatically wrapped with `?` by
| some custom error type specific to your crate that
| conveys more information about what went wrong. This is
| phenomenal for errors that need to be bubbled up to end-
| users.
|
| Second, for the majority of errors that are normal,
| expected, and recoverable, annotating them is just
| pointless busywork since they'll never be visible from
| outside of your program. For example, errors that
| eventually bubble up to an `.ok_or(...)` receive zero
| benefit from being annotated.
|
| Third, is your preferred alternative the Go approach
| where you function as a less-capable human exception
| handler? Having to hunt through the source to identify
| what actually happened through some contortionist `error:
| thing went wrong: subsystem died: api client failed:
| gcloud client: cache error: filesystem error: file not
| found: tmp.VRVcBX1j` with no line numbers or function
| names, and various random components of the error string
| coming from either third-party libraries or the golang
| standard library? This is just so comically terrible to
| anyone who's spent time in languages with decent error
| handling it's genuinely hard to believe that people
| regularly come to its defense.
|
| But of course I'm being generous here when we both know
| the actual status quo in the overwhelming majority of
| production Go projects is to simply bubble up the error
| with `return nil, err` with no context whatsoever, so you
| just get `error: file not found: tmp.VRVcBX1j` with
| absolutely no idea of where it came from. Those are
| always my favorite.
|
| So, to recap: with Rust's `?` operator you actually _can_
| have your cake and eat it too. You can add library-
| specific context to your errors while actually wrapping
| the underlying error and not merely mashing strings
| together. You can opt into stack traces for your own code
| if you want to. And you can skip the annotations for code
| where you handle errors and don 't bubble them up. The
| only apparent downside is that it's not overly verbose
| enough for Go adherents.
| LandR wrote:
| Go developers aren't happy unless they are taking 10
| lines of code and turning it into 150.
| cytzol wrote:
| I like Go. It's useful for the things I need it for since it
| compiles fast into a single binary and has networking
| utilities in its standard library. I was used to Rust's error
| handling when I started, but I liked how simple Go's design
| was in comparison, so I stuck with it to get a proper feel
| for the language.
|
| 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.
| xxpor wrote:
| So wait, go has handle errors by returning them, but it
| also doesn't force you to actually handle all return
| values? I thought that was the entire point of implementing
| error handling like that.
|
| How are we still repeating the same mistakes C made 50
| years ago?
| geoka9 wrote:
| > I don't see how anyone can see this as anything other
| than a big ugly wart on the face of the language.
|
| Would you be satisfied if the compiler forced you to check
| error returns?
| ghayes wrote:
| It might also be self-selection that people that truly
| dislike the error handling simply avoid golang. I'd really be
| interested to see how well go generics handle the Result
| type.
| jatone wrote:
| meme. its like the least annoying thing I deal with on a
| daily bases when programming. oh no... I have to handle an
| error....
| eweise wrote:
| They probably stop complaining after a few weeks because
| there's no use. It is what it is.
| topicseed wrote:
| What would you like for error handling?
| spion wrote:
| See Swift for a much more reasonable way to do error handling
| https://docs.swift.org/swift-
| book/LanguageGuide/ErrorHandlin...
| pkaye wrote:
| Personally I think the documentation should enumerate all the
| error types returned by functions.
| bruce343434 wrote:
| How does Go handle the ambiguity between [] meaning generics and
| [] also meaning array?
| pphysch wrote:
| You can use optional(?) parenthesis to make it extra clear.
|
| []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.
| uluyol wrote:
| In most cases there is no parsing ambiguity. In the cases where
| there are, you need to use parenthesis to clarify.
| unix1 wrote:
| I too was playing around with Go generics. I wrote some naive
| concurrent filter and fold (reduce) functions for slices and maps
| here https://github.com/unix1/gostdx if anyone is curious how
| those would feel.
| Laremere wrote:
| My opinion with 9+ years since first learning Go, multiple of
| those using it for a full time job:
|
| Putting 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.
| jamespwilliams wrote:
| One annoying bit about Go's generics is that you can use type
| parameters in functions, but not in methods.
|
| 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.
| kubb wrote:
| There is another way, you can declare option as
| 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.
| tubby12345 wrote:
| >One annoying bit about Go's generics is that you can't use
| type parameters in methods.
|
| 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...?
| jamespwilliams wrote:
| You can use type parameters in functions, but not methods.
| Methods are functions which have a receiver, so:
| // 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.
| tubby12345 wrote:
| so does this mean there are no generic structs in generic
| go?
| uluyol wrote:
| You can do type X[T any] struct{}
| func (x *X[T]) Method() {}
|
| just fine. What you can't do is func (x
| *X[T]) Method[T2 any]() {}
| loopz wrote:
| Not saying mixing OO and generics could never have any
| merit, but.. Isn't a method just a function having an
| object as first parameter. Does Go change this beyond
| "syntactic sugar" somehow? Been a while from coding Go,
| so interested to hear.
|
| The rationale seems to me that generics be functions
| first (ok, procedural), and not complecting it with
| objects and OO too much, whatever that mix could mean..
| klodolph wrote:
| > Isn't a method just a function having an object as
| first parameter.
|
| Differences:
|
| - Methods must be defined in the same package as the
| receiver.
|
| - Methods can be used to implement interfaces.
|
| - Methods can be discovered dynamically by inspecting the
| type of the receiver (either through reflection or with a
| dynamic cast).
| egeozcan wrote:
| In other words, methods cannot _introduce_ generic types.
___________________________________________________________________
(page generated 2021-12-16 23:00 UTC)