4 ms·
While it's true that some find it liberating, a large number don't — personally, I've written a fair amount of production Go code and found it unnecessarily ver
by reissbaker 5y ago
While it's true that some find it liberating, a large number don't — personally, I've written a fair amount of production Go code and found it unnecessarily verbose and repetitive in ways that generics would've helped. I imagine some of this is based on problem domain; if you're writing a web application for example, maybe you don't really need generics much. After all, how often do you need a function that logs in a user to also log in... a book you're selling? Not very often. And any advanced data structures probably live in your database: how much do you really need a B-tree in a webapp when you've already got one in MySQL?
That being said for a lot of other uses, you really do want high quality data structures beyond "array" and "dictionary."
- theshrike79 5y agoYou say "verbose and repetitive", I say "easy to read without any surprises". The verbose patterns (if err!= nil for example) make the code predictable to read, you notice the code smell of missing error handling really fast.
- coryrc 5y agoOr use Option or Result, so your error handling is small and forcibly correct.
- tptacek 5y agoError handling in Rust isn't always small or straightforward. I miss both options and match expressions when I switch back to Go from Rust, but there's also tangles of or_else's and maps and the fact that everyone uses third-party libraries to work out the types for errors. There's tradeoffs everywhere you look.
- int_19h 5y agoZig is probably a better comparison for this: https://ziglang.org/documentation/master/#Errors https://ziglang.org/documentation/master/#Errors
- takeda 5y agoI think readability has multiple dimensions, and it really depends what you are looking for. For example here's a code in Go to look for a Prime: func IsPrime(n int) bool { if n < 0 { n = -n } switch { case n < 2: return false default: for i := 2; i < n; i++ { if n%i == 0 { return false } } } return true } It's readable as it is simple to understand what each line does. Here for example is a code that does the same thing in Rust: fn is_prime(n: u64) -> bool { match n { 0...1 => false, _ => !(2..n).any(|d| n % d == 0), } } It's might seem more complex at first (what does match do, what 0...1 means, !(2..n) what is any() doing. But if you understand the language it actually this seem much simpler and you can quickly look at it and know exactly what it is doing. And because it is less verbose it is easier to grasp the bigger code. I also noticed that while individual functions in Go are simple to understand and follow, you can still create complex, hard to follow and understand programs in Go.
- bopbeepboop 5y agoYou changed function signatures from int->bool to uint->bool, which changes how long the functions are. That seems unfair when comparing: Removing negatives from the Go implementation removes 6 out of 16 lines, bring it from 3x Rust to 2x Rust in line length.
- takeda 5y agoI used code from: https://endler.dev/2017/go-vs-rust/ https://endler.dev/2017/go-vs-rust/ good point, I overlooked that. Anyway in Go (ironically because of lack of generics) if you use any numeric type other than int, int64, float64 you will be in the word of hurt. Rust doesn't have that issue. So in practice you will likely use int, and I suppose you can add an assertion. BTW: I only see that it would remove 3 lines though, where are the other 3?
- tptacek 5y agoI don't follow. I use unsigned ints in Go all the time. I've never been in a world of hurt with them. Mandatory explicit integer conversions (and the way Go consts work) are something Go gets right.
- anothernewdude 5y agoYou're right that a templating language would reduce the code and make it easier to read.
- andy_ppp 5y agoPersonally I find code with generics just as easy to read as (largely duplicated) implementations for each type. I might consider Golang again when this drops and there are things to be like about how low level it is…
- the_gipsy 5y agoIn rust you have to swallow an error quite explicitly. In go it’s extremely easy to swallow one "err”, by assigning to some previous one which was already checked.
- dmitriid 5y ago> if you're writing a web application for example, maybe you don't really need generics much All our microservices return a { result: ... } or a { result: ..., nextPageToken } We would definitely benefit from generic SingleResult<T> and PagedResult<T>. Instead, you copy-paste the same definitions over, and over, and over, and over again for every call.
- jrockway 5y agoI think I see what you're doing. Right now, each result type implements NextPager, which returns information about how to fetch the next page. You client can implement a utility like FetchNextPage: type NextPager interface { NextPage() PageSpec } func (c *Client) FetchNextPage(ctx context.Context, current NextPager) (interface{}, error) { ... } Then for each type of paged object, you write: func (c *FooClient) FetchNextFoo(ctx context.Context, current Foo) (Foo, error) { next, err := c.client.FetchNextPage(ctx, current) ... if n, ok := next.(Foo); ok { return n, nil } return Foo{}, fmt.Errorf("unexpected type: got %T, want Foo", next) } That's annoying. But, this problem has come up before with `sql`, which has rows.Next() and rows.Scan() to iterate over arbitrary row types, and you could use that as a model: pages := client.Query(...) defer pages.Close() for pages.Next() { var foo Foo if err := pages.Scan(&foo); err != nil { ... } // do something with the page } Generics would let you enforce the type of `foo` at compile time, but it wouldn't save you many lines of code. I think you still have to write (or generate) a function like `func (c *Client) ListFoos(ctx context.Context, req ListFooRequest) (Paged[Foo], error) { ... }`. We hand-wave over that in the above example with a "..." passed to query (potentially possible if you retrive objects with a stringified query, like SQL or GraphQL), but that sounds like the hard and tedious part. Let me conclude with a recommendation for gRPC and gRPC-gateway as a bridge to clients that don't want to speak gRPC. Then you can just return a "stream Foo", and the hard work is done for you. You call stream.Next() and get a Foo object ;)
- dmitriid 5y ago> Generics would let you enforce the type of `foo` at compile time, but it wouldn't save you many lines of code. You literally showed that "for each type of paged object, you write <multiple lines of entirely unnecessary code>". Where with generics you just have a single generic function. > We hand-wave over that in the above example with The problem is: there's no hand-waving in reality.
- tptacek 5y agoSure, that's true, and it's fine. There are people who find s-expressions both liberating and clarifying, and others who get lost in a sea of parentheses. And there are problems that are especially amenable to s-expressions, or generics, or fine-grained control of memory allocation, or interwoven code and markup, object hierarchies, and those problems sort of "ask" for particular languages. But for the most part, this stuff is subjective.
- jolux 5y agoThere are certain things that just can’t be done without generics, though. Type safe higher order functions, type safe custom collections, etc. Of course, perhaps these are all just subjective to you, because you can still write any program you need without them. But not having this feature does constrain the set of type-safe programs you can write quite a bit.
- tptacek 5y agoI feel like it pretty much always turns out that the things you can't do without generics are, like, second-order things. Higher order functions, type safe custom collections, those are tools. What we care about mostly is what we actually build, and people build pretty much everything in every language, generics or not.
- jolux 5y agoIt depends what you’re building. If you’re writing a library to do those things, they may well be first order. Certain extremely useful patterns like parser combinators rely on them. And of course, the current answer is just “do something else instead.” But I don’t really see why this has to be the answer. Surely the decision to leave them out is just as subjective as a decision to add them.
- dmix 5y agoTo add to your point, not using generics in Go is a choice too, even if it’s now an option. But some people like offloading that to the language: not having it in a language means you don’t have to control for it on your team/project contributors + some 3rd party library. I felt Go filled this minimalist category well. It’s always nice having a modern mainstream option doing so, not just a niche one on the fringes (ala LISP), even if I’m not personally a fan. I guess it’s hard to keep saying no.