'How to create generic method in Go? (method must have no type parameters)
Golang 1.18beta supports generic, I want to add an extension method on a generic slice. e.g. a map function is defined as this:
func Map[E, V any](slice *[]E, iteratee func(E) V) *[]V {
result := []V{}
for _, item := range *slice {
result = append(result, iteratee(item))
}
return &result
}
Then I want to make this method as an extension method of slice, something like this, but cannot compile successfully:
func (slice *[]E) Map[E, V any](iteratee func(E) V) *[]V {
result := []V{}
for _, item := range *slice {
result = append(result, iteratee(item))
}
return &result
}
go build
with Go 1.18 gives the error:
main.go: method must have no type parameters
What is the correct way to implement the second code block?
I want to use like this:
slice := []string{"a", "b", "c"}
newSlice := slice.Map(func(s string) string {
return s + "122"
})
Solution 1:[1]
You have to declare the slice
type with type parameters you want to use for the Map()
(or for any other) method:
type slice[E, V any] []E
And you have to add the type parameters to the receiver, without the constraints (those will be the same as defined at the type declaration), as if you'd instantiate the generic slice
type:
func (s *slice[E, V]) Map(iteratee func(E) V) *[]V {
result := []V{}
for _, item := range *s {
result = append(result, iteratee(item))
}
return &result
}
Testing it:
s := slice[int, string]{1, 2, 3}
m := s.Map(func(i int) string { return fmt.Sprint("x", i) })
fmt.Println(m)
Which will output (try it on the Go Playground):
&[x1 x2 x3]
Also note that slices (slice headers) already contain a pointer to an underlying array, so it's rare that you need to use a pointer to slice. Instead declare the method with non-pointer receiver, and return a non-pointer (try this one on the Go Playground):
func (s slice[E, V]) Map(iteratee func(E) V) []V {
result := []V{}
for _, item := range s {
result = append(result, iteratee(item))
}
return result
}
Relevant section from the (tip) spec: Spec: Method declarations:
If the receiver base type is a parameterized type, the receiver specification must declare corresponding type parameters for the method to use. This makes the receiver type parameters available to the method.
Syntactically, this type parameter declaration looks like an instantiation of the receiver base type, except that the type arguments are the type parameters being declared, one for each type parameter of the receiver base type. The type parameter names do not need to match their corresponding parameter names in the receiver base type definition, and all non-blank parameter names must be unique in the receiver parameter section and the method signature. The receiver type parameter constraints are implied by the receiver base type definition: corresponding type parameters have corresponding constraints.
Solution 2:[2]
Methods are not permitted to have type params not specified on the type of the receiver.
The reason is that interfaces in Go are implemented implicitly, so it is yet unclear how a generic method would implement interfaces.
In order to use the second type param V
, it must be defined on the type declaration. For example:
// type def: must specify V here
type Slice[T, V any] []T
func (s Slice[T,V]) Map(iteratee func(T) V) []V {
result := make([]V, 0)
for _, item := range s {
result = append(result, iteratee(item))
}
return result
}
However you might also consider using a generic top-level function, that has the advantage of permitting type inference, thus allowing you to have less verbose code and omit explicit type params during the instantiation.
Note that the receiver must repeat the type parameter declaration, so that type parameter names are in scope for the method:
func (s Slice[T,V]) Map(...)
If some method does not use a type parameter at all, you can signal that with the blank identifier _
(underscore):
func (s Slice[T,_]) Foo( /* not using V herein */ )
However what your question was actually about is defining a generic method on an unnamed type.
You can't do this with or without generics. You can only declare methods on defined types (i.e. types with identifiers), and in your second snippet, E
is not a defined type, it's a type parameter.
Unlike Javascript where all arrays inherit the Array
prototype, a slice type, in itself, in Go isn't defined either. It's formalized in the language specs as:
SliceType = "[" "]" ElementType .
As you can see there's no identifier. So you can't declare a method on any and all slices, because there isn't a catch-all type that can be the receiver for such method.
Playground showing method vs func: https://gotipplay.golang.org/p/R7uAq0SBUjj
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 |