Go Generics
Summary: Overall, Go generics (also called as type parameters) is good, but not so good. Generics on types & functions are supported, but methods are not - which causes a lot of limitations.
This post is just my very first feelings about the generics feature of Go after some simple experimentation, it's not a full picture of what generics in Go looks like, if you want to see the full picture, check this very long spec.
The Good Things #
Now you don't need to write function like max again and again for different data types:
func max[T constraints.Ordered](a T, b T) T {
if a > b {
return a
}
return b
}
max(1, 2) // 2
max(4.5, 3.9) // 4.5
Playground
And you can write a generics tree which can be only achieved in the past using interface{}
type tree[T any] struct {
Left *tree[T]
Right *tree[T]
Value T
}
t := tree[int]{}
t1 := tree[string]{}
Playground
Typed parameters can be named or unnamed
func Index[T interface{ Equal(T) bool }](s []T, e T) int {
for i, v := range s {
if e.Equal(v) {
return i
}
}
return -1
}
func Index1[T Equaler[T]](s []T, e T) int {
for i, v := range s {
if e.Equal(v) {
return i
}
}
return -1
}
type Equaler[T any] interface {
Equal(T) bool
}
Playground
We can also union multiple types together
type numbers interface {
int | int8 | int32 | int64 | float32 | float64
}
func min[T numbers](a, b T) T {
if a > b {
return b
}
return a
}
min(2,3)
min(2.4, 0.5)
Playground
~ operator can be used to "include" all underlying types
type degree int
type numbers interface {
~int | ~int8 | ~int32 | ~int64 | ~float32 | ~float64
}
func min[T numbers](a, b T) T {
if a > b {
return b
}
return a
}
min(degree(2), degree(3))
min(2.4, 0.5)
Playground
comparable interface can be used for writing comparable functions:
func equal[T comparable](a, b T) bool {
return a == b
}
equal(degree(2), degree(3))
equal(degree(2), degree(2))
equal(2.4, 0.5)
equal(2.3, 2.3)
Playground
The Limitations #
Methods cannot be generics
What does it mean when methods cannot be generics? It means you will not be able to write a map or reduce API like Java. The main reason for not supporting this feature is because of typed parameters only known by the compiler, the runtime has no ideas about type parameters and this introduces some complexity for the instantiation during run time .
You can see the full explanation from the Go team here, it's pretty much well explained. But I personally don't really agree with them when they say "one of the main roles of methods is to permit types to implement interfaces" and used this to argue about not including generics on methods. To me, methods are more important than just to "implement" an interface.
So any alternative way for writing map/reduce like API? Yes, use functions instead.
func Map[T1, T2 any](s []T1, f func(T1) T2) []T2 {
r := make([]T2, len(s))
for i, v := range s {
r[i] = f(v)
}
return r
}
func Reduce[T1, T2 any](s []T1, initializer T2, f func(T2, T1) T2) T2 {
r := initializer
for _, v := range s {
r = f(r, v)
}
return r
}
More Detail
Mixed types cannot be inferred as any
Typed parameters mean all parameters must be the same type. And the inference rule of Go is that it will check the type of the first parameter, others must follow the same. What does it mean? It means if you don't declare the first parameter as any
explicitly, it will never be inferred as any
.
func Print[T any](t ...T) {}
var a any = 1
Print(a, "2")
Print[any](1, "2")
Print(1, "2")
Playground
Although we can provide the type parameter when calling the method, but it is kind of inconvenient.
[T ~[]any] and [S ~[]T, T any] Are Different
This is not really a limitation, it is just a mistake people who first started with generics often face. The difference between the two declarations is the first one declares any
as a specific type, the latter declares it as a parameter type.
[T ~[]any] only accepts []any, but [S ~[]T, T any] will accept any kind of slices, such as: []int, []string, []any,...
Interfaces with methods can be used as a typed parameters, but cannot be "union"
Yes, it is. But what's the point of having union interfaces? I don't really know the usage of this case.