具有T类型和接受T类型参数的命名函数类型的泛型。

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

generics with a T type and a named function type that takes T

问题

我有一个定义了一堆类型的模块:

type Thing1 struct {}

type Thing2 struct {}

还有以上述类型作为参数的命名函数类型:

type T1 func(t *Thing1)
type T2 func(t *Thing2)

然后,它使用这些函数类型定义了一个映射:

var (
    ModThing1 = map[string]T1{ ... }
    ModThing2 = map[string]T2{ ... }
)

在使用这个模块的应用程序中,我想要为Thing1Thing2使用泛型。

类似这样:

func do[T any](in *T, inMap map[string]func(in *T)) {
  for _, val := range inMap {
     val(in)
  }
}
...
do[mod.Thing1](&mod.Thing1{}, mod.ModThing1)

当然问题是,Go不允许这样做,因为映射的值类型与mod.ModThing1的值类型不同。func(in *T))mod.T1不同。

有办法让这个工作吗?

英文:

I have a module that defines a bunch of types

type Thing1 struct {}

type Thing2 struct {}

and named functions types that take the types above as arguments

type T1 func(t *Thing1)
type T2 func(t *Thing2)

Then it defines a map using these function types

var (
    ModThing1 = map[string]T1{ ... }
    ModThing2 = map[string]T2{ ... }
)

In my app that uses this module, I would like use a generic for Thing1 and Thing2

Something like:

func do[T any](in *T, inMap map[string]func(in *T)) {
  for _, val := range inMap {
     val(in)
  }
}
...
do[mod.Thing1](&mod.Thing1{}, mod.ModThing1)

Of course the problem is that Go wont allow this because the type of the value of the map is not the same as mod.ModThing1 value type. func(in *T)) vs mod.T1

Is there a way to get this to work?

答案1

得分: 2

在函数do中,声明一个额外的类型参数F,其约束条件近似地引用了T。你可以利用T1T2具有相似的底层类型。你甚至不需要显式实例化do的类型参数,两者都可以被推断出来。

func main() {
	do(&foo.Thing1{}, foo.ModThing1)
	do(&foo.Thing2{}, foo.ModThing2)
}

func do[T any, F ~func(*T)](in *T, inMap map[string]F) {
	for _, val := range inMap {
		val(in)
	}
}

Playground: https://go.dev/play/p/wiWWzXVDG7v

如果你对导入的包有控制权,另一种解决方案是使用泛型函数类型代替T1T2。这个抽象的原理是相同的。实际上,在这里你可以更清楚地看到为什么第一个解决方案有效:

type F[T any] func(t *T)

// 替代
// type T1 func(t *Thing1)
// type T2 func(t *Thing2)

然后,你使用特定实例化的F[T]声明map变量:

var (
	ModThing1 = map[string]F[Thing1]{ ... }
	ModThing2 = map[string]F[Thing2]{ ... }
)

然后在do函数中,你用类型参数实例化F

func do[T any](in *T, inMap map[string]F[T]) {
	for _, val := range inMap {
		val(in)
	}
}

Playground: https://go.dev/play/p/ITBqiqjjVUz

顺便说一下,你还可以对map类型进行抽象:

type M[T any] map[string]F[T]

func do[T any](in *T, inMap M[T]) {
	for _, val := range inMap {
		val(in)
	}
}
英文:

In the function do, declare an additional type parameter F with an approximated constraint that also references T. You take advantage of T1 and T2 having similar underlying types. You don't even have to explicitly instantiate do's type arguments, both can be inferred.

func main() {
	do(&foo.Thing1{}, foo.ModThing1)
	do(&foo.Thing2{}, foo.ModThing2)
}

func do[T any, F ~func(*T)](in *T, inMap map[string]F) {
	for _, val := range inMap {
		val(in)
	}
}

Playground: https://go.dev/play/p/wiWWzXVDG7v

<hr>

If you have control over the imported package, another solution is to use a generic function type instead of T1 and T2. The principle behind this abstraction is the same. In fact, here you can see more clearly why the first solution works:

type F[T any] func(t *T)

// instead of
// type T1 func(t *Thing1)
// type T2 func(t *Thing2)

Then you declare the map variables with specific instantiations of F[T]:

var (
	ModThing1 = map[string]F[Thing1]{ ... }
	ModThing2 = map[string]F[Thing2]{ ... }
)

Then in the do function you instantiate F with the type parameter:

func do[T any](in *T, inMap map[string]F[T]) {
	for _, val := range inMap {
		val(in)
	}
}

Playground: https://go.dev/play/p/ITBqiqjjVUz

<hr>

By the way, you can also abstract the map type:

type M[T any] map[string]F[T]

func do[T any](in *T, inMap M[T]) {
	for _, val := range inMap {
		val(in)
	}
}

huangapple
  • 本文由 发表于 2023年1月27日 21:48:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/75258993.html
匿名

发表评论

匿名网友

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

确定