How to create generic method in Go? (method must have no type parameters)

huangapple go评论71阅读模式
英文:

How to create generic method in Go? (method must have no type parameters)

问题

Golang 1.18beta支持泛型,我想在泛型切片上添加一个扩展方法。例如,一个map函数可以定义如下:

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
}

然后我想将这个方法作为切片的扩展方法,类似于这样,但是无法成功编译:

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 1.18进行go build会出现以下错误:

main.go: method must have no type parameters

请问如何正确实现第二个代码块?

我想这样使用:

slice := []string{"a", "b", "c"}
newSlice := slice.Map(func(s string) string {
		return s + "122"
	})
英文:

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"
	})

答案1

得分: 28

你必须使用类型参数声明slice类型,以便在Map()(或任何其他)方法中使用:

type slice[E, V any] []E

并且你必须将类型参数添加到接收器中,不需要添加约束(这些约束与类型声明中定义的相同),就像你实例化泛型的slice类型一样:

func (s *slice[E, V]) Map(iteratee func(E) V) *[]V {
    result := []V{}
    for _, item := range *s {
        result = append(result, iteratee(item))
    }

    return &result
}

进行测试:

s := slice[int, string]{1, 2, 3}
m := s.Map(func(i int) string { return fmt.Sprint("x", i) })
fmt.Println(m)

输出结果为(在Go Playground上尝试):

&[x1 x2 x3]

还要注意,切片(切片头部)已经包含指向底层数组的指针,所以很少需要使用切片的指针。相反,使用非指针接收器声明方法,并返回非指针(在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
}

相关规范的章节:规范:方法声明

如果接收器的基本类型是参数化类型,则接收器规范必须声明相应的类型参数供方法使用。这使得接收器类型参数在方法中可用。

从语法上讲,这个类型参数声明看起来像是接收器基本类型的一个实例化,只是类型参数是正在声明的类型参数,每个类型参数对应于接收器基本类型的一个类型参数。类型参数的名称不需要与接收器基本类型定义中的相应参数名称匹配,并且在接收器参数部分和方法签名中,所有非空白参数名称必须是唯一的。接收器类型参数的约束由接收器基本类型定义隐含:相应的类型参数具有相应的约束。

英文:

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.

答案2

得分: 16

方法不允许具有未在接收器类型上指定的类型参数

原因是Go中的接口是隐式实现的,因此尚不清楚通用方法如何实现接口。

为了使用第二个类型参数V,它必须在类型声明中定义。例如:

// 类型定义:必须在这里指定V
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
}

但是你也可以考虑使用一个通用的顶层函数,它具有允许类型推断的优势,从而允许你编写更简洁的代码并在实例化时省略显式的类型参数。

请注意,接收器必须重复类型参数声明,以便方法中的类型参数名称在作用域内:

func (s Slice[T,V]) Map(...)

如果某个方法根本不使用类型参数,你可以使用下划线 _ 来表示:

func (s Slice[T,_]) Foo( /* 在此处不使用 V */ )

<hr>

然而,你实际上的问题是关于在未命名类型上定义通用方法。

你无论是否使用泛型都无法做到这一点。你只能在定义的类型上声明方法(即具有标识符的类型),而在你的第二个代码片段中,E 不是一个定义的类型,它是一个类型参数

与JavaScript不同,其中所有数组都继承自Array原型,Go中的切片类型本身也没有定义。它在语言规范中被正式化为:

SliceType = "[" "]" ElementType .

正如你所看到的,这里没有标识符。因此,你不能在任何和所有切片上声明方法,因为没有一个可以成为此类方法的接收器的通用类型。

Playground展示了方法与函数的区别:https://gotipplay.golang.org/p/R7uAq0SBUjj

英文:

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 */ )

<hr>

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 = &quot;[&quot; &quot;]&quot; 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

huangapple
  • 本文由 发表于 2022年1月11日 22:10:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/70668236.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定