I think Dave’s point about intellectual honesty and a clear explanation and position is great, because it would to some discussion of the mapping between Go and various problems that generics resolve. Code generation is not “wrong”. It can be made really nice to work with – and although Swift, Rust and Scala all have rich type systems with generics, the latter two also have macrosystems of considerable sophistication, and code generation is an accepted methodology in Swift (for things like asset lists).
Three things I wonder about, with regards to not having generics:
Why are generics okay for arrays, maps and channels but not anything else?
What’s a good story for doing things like generic matrices, Maybe (nullability) and Either (capturing and propagating error values)? If it’s all code generation, for example, it could be pretty terrible to work with. MaybeInt, MaybeByte, MaybeUserDefinedType…
One thing that generics provide is type tagging of things like JSON extractors and SQL expressions. For example, you can build a SQL expression using Scala’s Doobie, where you know it returns a timestamp, and later use that expression in your code safely since the type annotation is there to help you. This “phantom type” is more or less a “trust me” declaration – it’s helpful when stitching things together. This seems like a different problem from generic containers since there is no actual layout of the underlying data values – a SQL expression is not really a struct.
This isn’t accurate – nullability and error propagation are cross-cutting concerns that occur in many languages. For example, Swift and Kotlin both provide shortcuts for nullability. In Java you see them handled with annotations. There’s nothing peculiar to Haskell about these issues.
If you mean, using a generic type for them is “the Haskell way” – well, that is true; but using a generic type for matrices is also the Haskell way.
Yes, what Rob Pike describes this, where the first error is terminal, is like Maybe and Either. It is rather more like Either, since we capture the error value.
Or maybe some people would like generics, but value other things more?
Go has one of the easiest to use automatically enabled H2 stacks. I run a small CDN, and all of my customers got H2 “for free” because net/http transparently supports it. It has a wonderful runtime that allows you to treat threads as almost free, while giving you great performance for network servers. Sync semantics in async code are a huge productivity booster. That’s why js people love their await keyword so much.
FFI in Go is better than any mainstream language I’ve worked with (java jni, Python ctypes/cffi).
The race detector is awesome. I honestly don’t understand how anyone would be comfortable writing multithreaded code without something like that (I know, I know, races are impossible in Rust and Haskell).
The rest of the tooling is just so great. The builtin test module is fine, but the integration with the race detector and CPU profiler and memory profiler and mutex profiler and code coverage reporting is amazing.
The standard lib is a joy to read. So much better than Python or node where most functions are really just wrappers around blobs in another language.
In my opinion large parts of the entire SDLC are made easier with go. Writing, building, testing, optimizing, and deploying.
None of this is to say go is better or worse than other language/runtime/library ecosystem combos. Rust is great (I keep coming back to it to bang my head on that wall. It’s hard). Haskell is beautiful. Erlang is so well engineered and amazing. The jvm is a fast runtime with amazing levels of introspectability. Clojure has beautiful persistant data structures.
I’d love generics and better compiler support for immutability and I’m still using Go because it’s got some other things going for it.
The reddit thread shows how much people are divided on the issue. I wonder if it’s along the lines of library writer (pro-generics) and app writer (no need for generics)?
Indeed, but I think the last point rings very much true:
However, if the decision is not to add templated types, then it should be made so explicitly. Then it is incumbent upon all Go programmers to explain the Go Way of solving problems.
It would be good to settle this issue for once and for good. Be bold and say ‘no generics in the foreseeable future’ or just do it. But the current state of limbo is annoying to Go users who would like to see generics. (The lack of generics was one of the few reasons I have mostly moved away from Go.)
My guess is that generics is not a feature to many developers, it’s a strategy to solve problems. For example, you look at APIs differently when you have parametric polymorphism. A way you do not look at them if you don’t. I am not optimistic that Go 2 not having generics and being explicit about it will shut people up.
Golang currently is clear on its stance on generics, there is even a FAQ item on it, yet it’s a hot topic in the language. My point is that I don’t think being clearer on it will make it a less hot topic. Many people are still going to have to use Golang and be upset about the lack of generics and since they have no option but to use it will make blog posts about it.
Dave isn’t just saying “Go needs a clearer stance on generics”, though. He’s also saying “Go’s designers need to show how the business problems generics address can also be addressed in other ways”, which they apparently haven’t done in a convincing enough way to silence generics advocates.
(Not that I think this is actually possible: the absence of parametric polymorphism makes certain things fundamentally impossible to say.)
But their stance keeps everyone in the dark and is quite disingenious:
Generics may well be added at some point. We don’t feel an urgency for them, although we understand some programmers do. […] Generics are convenient but they come at a cost in complexity in the type system and run-time. We haven’t yet found a design that gives value proportionate to the complexity, although we continue to think about it.
Parametric polymorphism is well-understood, many different approaches have been tried in different languages. Parametric polymorphism has even been tried in combination with structural type systems (e.g. Ocaml). Parametric polymorphism allows for more abstraction and increases size of the ‘design space’. So, you either choose to make your language more powerful yet more complex, or you don’t. But this just discredits the decades of work of the PL community for (possibly) pragmatic/strategic reasons.
I guess that at this point it may also simply be too late. They probably have to make incompatible changes to the standard library to support parametric polymorphism throughout or they will end up with a compromised design as in Java.
Go will do perfectly fine without generics (it’s has been doing fine for years), but just make that choice. Then everyone knows what to expect.
The list of strengths of parametric polymorphism you give has nothing to do with if the Golang designers decide to implement it or not. That list is true even if they continue on their decision not to implement it. I don’t seen a reason to believe people won’t continue to provide that list even if the Golang designers explicitly say they don’t want generics.
The go tools just look for files on disk and don’t care how they get there.
4 years ago I got the files there via submodules with a makefile target to update each; today I use gb but the old way works better if you want fine grained control.
They suffer from a similar problem that your desire for referencing git hashes in .go files would have, though, which is merging and rebasing become very challenging because a git hash has no semantic information about the contents of the hash so you either need tooling on top of whatever diffing tool you use to help understand if an update to the hash is correct or use some other form of versioning.
I think Dave’s point about intellectual honesty and a clear explanation and position is great, because it would to some discussion of the mapping between Go and various problems that generics resolve. Code generation is not “wrong”. It can be made really nice to work with – and although Swift, Rust and Scala all have rich type systems with generics, the latter two also have macrosystems of considerable sophistication, and code generation is an accepted methodology in Swift (for things like asset lists).
Three things I wonder about, with regards to not having generics:
Why are generics okay for arrays, maps and channels but not anything else?
What’s a good story for doing things like generic matrices,
Maybe
(nullability) andEither
(capturing and propagating error values)? If it’s all code generation, for example, it could be pretty terrible to work with.MaybeInt
,MaybeByte
,MaybeUserDefinedType
…One thing that generics provide is type tagging of things like JSON extractors and SQL expressions. For example, you can build a SQL expression using Scala’s Doobie, where you know it returns a timestamp, and later use that expression in your code safely since the type annotation is there to help you. This “phantom type” is more or less a “trust me” declaration – it’s helpful when stitching things together. This seems like a different problem from generic containers since there is no actual layout of the underlying data values – a SQL expression is not really a struct.
[Comment removed by author]
This isn’t accurate – nullability and error propagation are cross-cutting concerns that occur in many languages. For example, Swift and Kotlin both provide shortcuts for nullability. In Java you see them handled with annotations. There’s nothing peculiar to Haskell about these issues.
If you mean, using a generic type for them is “the Haskell way” – well, that is true; but using a generic type for matrices is also the Haskell way.
In Rob Pike’s “Errors as values” post he implements an ad-hoc, non composable, Maybe monday:
https://blog.golang.org/errors-are-values
It doesn’t seem quite accurate to say that this style of programming is not how you would do things in Golang.
Yes, what Rob Pike describes this, where the first error is terminal, is like
Maybe
andEither
. It is rather more likeEither
, since we capture the error value.I think most anyone who saw value in generics has already invested in using something besides Go.
I think you’re leaving out a lot of people who do not have control over what language they have to use on a daily bases.
I see lots of value in generics and lots of value in the rest of what go provides. I often choose go over generics. I’d rather have both though.
Or maybe some people would like generics, but value other things more?
Go has one of the easiest to use automatically enabled H2 stacks. I run a small CDN, and all of my customers got H2 “for free” because net/http transparently supports it. It has a wonderful runtime that allows you to treat threads as almost free, while giving you great performance for network servers. Sync semantics in async code are a huge productivity booster. That’s why js people love their await keyword so much.
FFI in Go is better than any mainstream language I’ve worked with (java jni, Python ctypes/cffi).
The race detector is awesome. I honestly don’t understand how anyone would be comfortable writing multithreaded code without something like that (I know, I know, races are impossible in Rust and Haskell).
The rest of the tooling is just so great. The builtin test module is fine, but the integration with the race detector and CPU profiler and memory profiler and mutex profiler and code coverage reporting is amazing.
The standard lib is a joy to read. So much better than Python or node where most functions are really just wrappers around blobs in another language.
In my opinion large parts of the entire SDLC are made easier with go. Writing, building, testing, optimizing, and deploying.
None of this is to say go is better or worse than other language/runtime/library ecosystem combos. Rust is great (I keep coming back to it to bang my head on that wall. It’s hard). Haskell is beautiful. Erlang is so well engineered and amazing. The jvm is a fast runtime with amazing levels of introspectability. Clojure has beautiful persistant data structures.
I’d love generics and better compiler support for immutability and I’m still using Go because it’s got some other things going for it.
I would s/value/necessity/ that.
The reddit thread shows how much people are divided on the issue. I wonder if it’s along the lines of library writer (pro-generics) and app writer (no need for generics)?
Not really much here…
Indeed, but I think the last point rings very much true:
However, if the decision is not to add templated types, then it should be made so explicitly. Then it is incumbent upon all Go programmers to explain the Go Way of solving problems.
It would be good to settle this issue for once and for good. Be bold and say ‘no generics in the foreseeable future’ or just do it. But the current state of limbo is annoying to Go users who would like to see generics. (The lack of generics was one of the few reasons I have mostly moved away from Go.)
My guess is that generics is not a feature to many developers, it’s a strategy to solve problems. For example, you look at APIs differently when you have parametric polymorphism. A way you do not look at them if you don’t. I am not optimistic that Go 2 not having generics and being explicit about it will shut people up.
This seems like a strange statement of purpose.
Golang currently is clear on its stance on generics, there is even a FAQ item on it, yet it’s a hot topic in the language. My point is that I don’t think being clearer on it will make it a less hot topic. Many people are still going to have to use Golang and be upset about the lack of generics and since they have no option but to use it will make blog posts about it.
Dave isn’t just saying “Go needs a clearer stance on generics”, though. He’s also saying “Go’s designers need to show how the business problems generics address can also be addressed in other ways”, which they apparently haven’t done in a convincing enough way to silence generics advocates.
(Not that I think this is actually possible: the absence of parametric polymorphism makes certain things fundamentally impossible to say.)
But their stance keeps everyone in the dark and is quite disingenious:
Generics may well be added at some point. We don’t feel an urgency for them, although we understand some programmers do. […] Generics are convenient but they come at a cost in complexity in the type system and run-time. We haven’t yet found a design that gives value proportionate to the complexity, although we continue to think about it.
Parametric polymorphism is well-understood, many different approaches have been tried in different languages. Parametric polymorphism has even been tried in combination with structural type systems (e.g. Ocaml). Parametric polymorphism allows for more abstraction and increases size of the ‘design space’. So, you either choose to make your language more powerful yet more complex, or you don’t. But this just discredits the decades of work of the PL community for (possibly) pragmatic/strategic reasons.
I guess that at this point it may also simply be too late. They probably have to make incompatible changes to the standard library to support parametric polymorphism throughout or they will end up with a compromised design as in Java.
Go will do perfectly fine without generics (it’s has been doing fine for years), but just make that choice. Then everyone knows what to expect.
The list of strengths of parametric polymorphism you give has nothing to do with if the Golang designers decide to implement it or not. That list is true even if they continue on their decision not to implement it. I don’t seen a reason to believe people won’t continue to provide that list even if the Golang designers explicitly say they don’t want generics.
Fair enough – it all seems reasonable as you have said it.
I’d personally much rather see:
[Comment removed by author]
I assume you mean you want a package manager to do that. There are a variety of ways to make that happen right now (eg git subtree/submodule)
[Comment removed by author]
The go tools just look for files on disk and don’t care how they get there.
4 years ago I got the files there via submodules with a makefile target to update each; today I use gb but the old way works better if you want fine grained control.
The big book covers them:
https://git-scm.com/book/en/v2/Git-Tools-Submodules
They suffer from a similar problem that your desire for referencing git hashes in .go files would have, though, which is merging and rebasing become very challenging because a git hash has no semantic information about the contents of the hash so you either need tooling on top of whatever diffing tool you use to help understand if an update to the hash is correct or use some other form of versioning.