How to create generic extension method for a generic slice in golang

Issue

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

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.

Answered By – icza

Answer Checked By – David Goodson (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.