An Introduction to Generics
go.dev
go.dev
So many simple daily use methods like map, forEach, any, all, reduce etc feel much better now.
On the whole generics here seem neat, well thought out, easy to understand and usable, while still allowing you to completely ignore them if you want. Most declarations fit neatly into libraries, with usage often never even having to mention or even know about type declarations (See the examples on the lib). That was a nice change from Java - the inference is pretty nice and neat from what I’ve seen so far.
I’m sure we’ll find it being pushed to its limits, like with ORMs and multi chaining, and that might be ugly, but on the whole I’m very satisfied with this addition.
For example instead of
func Drop[T any](array []T, n int) []T {
if n > len(array) {
n = len(array)
}
return array[n:]
}
Consider func Drop[T any](array []T, n int) []T {
if n < len(array) {
return array[n:]
}
return nil
}
Which can collect the underlying array sooner.Instead of your Chunk implementation (which, I want to offer, is better than most), consider an iterative zero-alloc version:
func Chunk[T any](array []T, n int) (chunk, remaining []T) {
return Take(array, n), Drop(array, n)
}
(And not performance-related, but instead of indices in `FindIndex` etc., consider returning pointers; they can then generalize more easily to non-slice sequences, or anonymous slices.)(And please don't call slices arrays.)
Didn't C already teach us this decades ago?
type Ordered interface {
Integer|Float|~string
}
I kind of despise the keyword / syntax design choice of the tilde (~) signifying "anything where the underlying type is e.g. a string".Usually a ~ signifies a bitwise-NOT operation*.
It reminds me of something more like a Ruby design mentality, where optimizing for code terseness is chosen. The nice thing about the crazy syntax in Ryby is that at least the chosen symbol is unique and doesn't conflict with meaning in other similar languages.
I realize the context where it's used is not part of the logic-execution pipeline, but it still forces me to contort my mind and special case this concept, and map it against what is effectively a namespace collision between Go and other C-style languages.
Naturally it's too late for Go (Generics are fully baked and released), but would have been nice to land with something more intuitive or at least less collision-prone, even if only to avoid the ambiguity.
Inevitably, the introduction of generics means increased opportunities for new kinds of complexity, and this choice needlessly increases the cognitive load when trying to read and reason about a go program.
I know it's the the end of the world, but I find it somewhat of a bummer for a technology I was previously so enthusiastic about.
* edit: Thank you Jtsummers for the correction- it is bitwise-NOT, I had mistakenly written bitwise-OR.
That sounded wrong to me, I recalled it being a NOT, and double checked. Turns out my recollection was correct (it's also not something I ever used much), it is the bitwise-NOT in C.
That actually makes it a bit more awkward, in my mind, as I initially parsed the example as "not string". However, ~ is also often used to mean "about" or "approximately". In a text exchange:
"How many people will be at dinner?"
"~5"
Meaning "about 5 people" or "approximately 5 people". So this also works in that sense, ~string can be read as "something in the neighborhood of a string" or "something like a string".
Only in expression context. C doesn't assign any meaning to ~ in type definition context, nor does it have an existing way to represent the concept of "any type reducible to X". Since it doesn't diverge from C in either syntactic or semantic meanings when considering context, I don't see this being an issue in practice. Any potential confusion argument you can lay onto ~ could also be used against |, but I don't see any mention of that being a problem.
I can't "disagree" with your reaction -- that would be silly even if I wanted to -- but I claim that you would likely stop suffering from increased mental effort as you read and write this syntax over time.
I bet most complaints we hear about generics in Java, have little to do with generics themselves and mostly to do with inheritance, abstract classes, and so on.
Inheritance based languages enable covariance and contravariance in generic types, something that comes in handy in many occasions.
Could you elaborate? Java should throw a runtime exception for mismatches.
But realistically, the above doesn't matter.
What actually matters is that you often _do_ have to cast at runtime in java, so it's somewhat common to hit type errors at runtime in practice. Haskell's type-system makes runtime type casting both less necessary, and vanishingly rare, meaning such runtime cases are practically never hit.
The above is at least true from my personal anecdotal experience.
Hm, could you please show me where you had to cast? Long ago, some resource/service lookup did require casting, but my anecdotal experience is that there really is no common area where I have to cast anything anymore.
So now the static expressivity of the type system is compromised in the name of runtime introspection. Reminiscent of how when Java added generics, runtime introspection ended up totally blind to them due to erasure.
This seems like the result of not taking care to account for the possible future addition of generics when originally designing the language — it was always well understood that they’d be a likely later addition to the language. The ability to check if a value satisfies an interface at runtime doesn’t seem all that critical, although I could be missing something, I’m not a regular user of the language.
One place it's absolutely critical, and certainly the most common by number-of-calls even if people don't think about it, is `fmt` functions that check to see if the type implements `String() string`.
An idiom found in many data-shuffling libraries is to accept a small interface like `io.Writer`, but have fast-paths if the type also implements other methods. E.g. https://cs.opensource.google/go/go/+/refs/tags/go1.18:src/io...
It's a little annoying but I don't think it's as bad as Java's erasure. We're still very early in idiomatic Go-with-generics so maybe we'll see a huge impact from this limitation later, but most times I see people wanting generic method parameters, they've got a design in mind which would be horribly inefficient even if it was valid. If you are going to box everything and generate lots of garbage, you might as well write Java to begin with.
package p1
type S struct{}
func (S) Identity[T any](v T) T { return v }
package p2
type HasIdentity[T any] interface {
Identity(T) T
}
package p3
import "p2"
// Because parameter v might be (and in this case is) coerced into
// p2.HasIdentity[int], this function gets a type annotation in the compiler
// output indicating that callers should pass a p2.HasIdentity[int] interface
// reference for v if it exists, or nil if it doesn't, as an additional
// argument.
func CheckIdentity(v interface{}) {
if vi, ok := v.(p2.HasIdentity[int]); ok {
if got := vi.Identity(0); got != 0 {
panic(got)
}
}
}
package p4
import (
"p1"
"p3"
)
func CheckSIdentity() {
p3.CheckIdentity(p1.S{})
}I think it would be terrible to include in the language because of the inconsistencies, but it is possible to make the two examples you listed as typical cases of interface->interface coercion work with generic methods, ugly as it may be.
If Go is really so bad, why don’t Go programmers use e.g. Rust? I’m sure they’d cite things like:
- Compilation time is much faster in Go.
- Complexity. Rust is a highly complex language even for me as a C++ programmer. For those who are new to compiled languages they are certainly going to choose Go over Rust
- GC definitely has development time benefits over a borrow checker if you can afford the extra CPU headroom and memory usage
- Go’s concurrency model is more appealing to many people
The real question is, why is there not a “Go but designed by sane people” language?
> The real question is, why is there not a “Go but designed by sane people” language?
The fact is that the JVM and .NET platforms would almost surely do better for anything that golang is considered for, but there's a lot of hype and companies want to attract programmrs.
Rust is complex due to the inherent complexity of being a low-level language.
"No way to require pointer methods" has been much more frustrating to work around. Even the part that works ("Pointer method example") sucks right now because type-type inference was disabled shortly before the 1.18 release pending some bugs. Hopefully once the basic ergonomics are back this can be reconsidered.
Everything just goes in a big circle. OOP is the bees knee's for like 20 years and then everyone decides that OOP is the devil for unnecessary abstraction. Then everyone gets all exited about functional programming, but they miss some of the convenience of abstract classes and inheritance.
After developers make sufficient noise, we wind up with effectively the same OOP functionality *GASP. We just avoid words like "class"....
EDIT: You guys are right about generics in FP. I guess my larger point is that lots language paradigms provide the same features just with different names and backlash is silly.
Also, it’s really not “OOP or functional”, there are tonnes of languages mixing OOP and functional very effectively (Scala is both very functional and very object oriented, but honestly most languages are a mix to some degree). Functional vs. imperative is more of a divide.
Many of the people targeting the most popular FP language of all time aren't writing the Javascripts because it's their first choice. Just look at the pretty WILD popularity of Typescript, an OOP bolt-on language which transpiles to Javascript.
The simpler (in some ways) imperative programming model still dominates developer mindset.
JavaScript is OOP already. TypeScript just provides better typing like what you would find in Haskell, Ocaml or other functional languages
I know lots of folks here on HN are super highly competent, and this contrasts with my IRL experience with colleagues who are less passionate about programming and just want to do their job- these folks aren't going to be teaching lessons in proper usage of objects in JS.
In truth, even classes are becoming increasingly uncommon in JavaScript codebases, and most object usage in JS these days is struct-like, where the ergonomics are pretty similar (or identical) to other languages.