Go泛型:为零参数的可变参数函数推断类型参数

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

Go generics: infer type parameter for variadic function with zero arguments

问题

假设我有一个带有两个泛型参数的函数,其中一个是可变参数:

func Constructor[F any, Opt any](f F, opts ...Opt) {}

如果我传入一些选项,调用这个函数是可以的:

Constructor(func() *myService { return ... }, 1, 2, 3)

然而,如果没有传入任何Opt,调用就会失败:

Constructor(func() *myService { return ... })

编译器会报错:

无法将'type func() *myService'(类型为func() *myService)用作类型(F, Opt)或F

我猜这是因为编译器在这种情况下无法确定Opt的类型。

虽然这是有道理的,但仍然很烦人。编译器并不需要Opt的类型,因为它是空的。

解决这个问题的一种方法是定义两个函数,ConstructorConstructorWithOpts。不过,只有一个函数会更好。有什么想法吗?

英文:

Let's say I have a function with two generic parameters, one of them variadic:

func Constructor[F any, Opt any](f F, opts ...Opt) {}

Calling this function works fine if I pass in a few options:

Constructor(func() *myService { return ... }, 1, 2, 3)

However, calling it without any Opts fails:

Construtor(func() *myService { return ... })

The compiler complains:
> Cannot use 'func() *myService' (type func() *myService) as the type (F, Opt) or F

I assume that’s because the compiler can’t figure out the type of Opt in this case.

While this makes sense, it’s annoying nevertheless. The compiler doesn't need the type of Opt, since it's empty.

One way to work around this is to define two functions, Constructor and ConstructorWithOpts. It would be really nice to just have a single function though. Any ideas?

答案1

得分: 2

编译器不需要Opt的类型,因为它是空的。

即使调用者决定提供零个参数,函数体仍然可以使用可变参数的值进行操作。因此,编译器肯定需要Opt的类型。

此外,类型推断的规则是清楚的:

类型推断基于以下内容:

  • 类型参数列表
  • 已知的类型参数的替换映射M(如果有)
  • (可能为空的)普通函数参数列表(仅适用于函数调用)

当某个类型参数的参数个数为零时,上述的第三个选项不适用。如果F和Opt完全不相关,那么第二个选项也不适用。

作为推论,考虑到如果调用者需要声明Constructor函数类型的,则必须提供所有的类型参数:

ctor := Constructor[func(), any] // 未调用!
ctor(f, 1, 2, 3)

最清晰的方法显然是让编译器推断F并明确指定Opts,尽管这需要反转函数声明中类型参数的顺序,以便客户端可以提供第一个参数并省略第二个参数。您可以定义一个any类型以提高可读性:

func Constructor[Opt any, F any](f F, opts ...Opt) {}

type NoOpts any // 仅用于更好的可读性

func main() {
    f := func() *myService { return ... }

    // NoOpts实例化Opt;F由编译器推断
    Constructor[NoOpts](f)
}

否则,只需使Constructor不可变参数:

func Constructor[F any](f F) {
    ConstructorWithOpts[F, any](f)
}

func ConstructorWithOpts[F any, Opt any](f F, opts ...Opt) {
    // ...
}
英文:

> The compiler doesn't need the type of Opt, since it's empty.

Even if the caller decides to supply zero arguments, the body of the function can still operate with the value of the variadic parameter. So the compiler definitely needs the type of Opt.

Furthermore the rules for type inference are clear:

> Type inference is based on
>
> - a type parameter list
> - a substitution map M initialized with the known
> type arguments, if any
> - a (possibly empty) list of ordinary function
> arguments (in case of a function call only)

When you have zero arguments for a certain type parameter, the third option as of above isn't applicable. If F and Opt are completely unrelated, the second option is also not applicable.

As a corollary, consider that the caller must supply all the type args if they ever need to declare a value of your Constructor function type:

ctor := Constructor[func(), any] // not called!
ctor(f, 1, 2, 3)

The cleanest is obviously to let the compiler infer F and specify Opts explicitly, although this requires inverting the order of the type parameters in the function declaration, so that clients can supply the first and omit the second. You may define an any type to improve readability:

func Constructor[Opt any, F any](f F, opts ...Opt) {}

type NoOpts any // just for better readability

func main() {
    f := func() *myService { return ... }

    // NoOpts instantiates Opt; F inferred
    Constructor[NoOpts](f)
}

Otherwise just make Constructor non-variadic:

func Constructor[F any](f F) {
    ConstructorWithOpts[F, any](f)
}

func ConstructorWithOpts[F any, Opt any](f F, opts ...Opt) {
    // ...
}

答案2

得分: 1

你可以为可变参数提供一个空切片:

Constructor(func() *myService { return nil }, []int{}...)

你也可以传递nil,但这样更冗长:

Constructor(func() *myService { return nil }, ([]int)(nil)...)

或者为类型参数提供值:

Constructor[func() *myService, any](func() *myService { return nil })

Go Playground上尝试一下。

英文:

You may provide an empty slice for the variadic argument:

Constructor(func() *myService { return nil }, []int{}...)

You can also pass nil, but it's more verbose:

Constructor(func() *myService { return nil }, ([]int)(nil)...)

Or provide values for the type parameters:

Constructor[func() *myService, any](func() *myService { return nil })

Try these on the Go Playground.

huangapple
  • 本文由 发表于 2022年11月5日 20:15:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/74327597.html
匿名

发表评论

匿名网友

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

确定