英文:
How to write a Go type constraint for something you can take len() of?
问题
我正在尝试为一个Go程序编写类型约束,该约束接受“任何你可以使用len()函数计算长度的东西”。但我真的搞不清楚该怎么做。
我想要的是这样的:
LenOf[m Measurable](m M) int {
return len(m)
}
我尝试了几种方法。比如这个天真的方法,它可以编译通过,但不能在所有类型上正常工作(比如[]User
):
type Measurable interface {
~string | []any | ~map[any]any
}
然后我尝试了下面这个方法,不仅使LenOf()
函数的签名变得非常笨重,而且在调用处写起来也很笨拙(而且我仍然无法使其编译通过):
type Measurable[K comparable, V any] interface {
~string | []V | ~map[K]V
}
英文:
I am trying to write a type constraint for a Go program, that accepts "anything you can take len() of". But I can't really figure it out.
I want something like:
LenOf[m Measurable](m M) int {
return len(m)
}
I tried a few things. Fx. this naiive thing, which do compile, but doesn't work on all types (like fx []User
):
type Measurable interface {
~string | []any | ~map[any]any
}
Then went on to something like, the below which not only makes the function signature for LenOf()
extremely clunky, but also have clumsy to write on call sites (and still can't get it to compile)
type Measurable[K comparable, V any] interface {
~string | []V | ~map[K]V
}
答案1
得分: 8
为什么?内置的 len
已经是“通用”的了。
话虽如此,让我们看看为什么定义这样一个约束是一个坏主意。Go 语言规范中有一段话 - 长度和容量,可以帮助我们:
如果参数类型是类型参数 P,调用
len(e)
(或cap(e)
)必须对 P 的类型集中的每个类型都有效。结果是与用 P 实例化时的类型参数对应的参数的长度(或容量)。
定义一个包含所有“可测量”类型的全面约束存在以下问题:
- 它包括数组
[N]T
,其中数组长度是类型的一部分,因此你的约束必须指定你想要捕获的所有可能的数组。 - 它包括数组指针
*[N]T
,你不能在类型约束中轻松地对其进行抽象。 - 它包括映射,这迫使你捕获键
K
和值V
,它们可能与T
相同也可能不同。此外,K
必须实现comparable
接口。
因此,你需要编写类似于以下的代码:
type Measurable[T any, K comparable, V any] interface {
~string | ~[]T | ~map[K]V | ~chan T
}
需要注意的是,这个代码并不包括数组,并且不能明确地捕获指针字面量,例如要匹配 []*int
,你必须使用 *int
实例化 T
。
你可以简化掉 V
:
type Measurable[T any, K comparable] interface {
~string | ~[]T | ~map[K]T | ~chan T
}
然后 LenOf
函数变成了:
func LenOf[T any, K comparable, M Measurable[T, K]](m M) int {
return len(m)
}
但是你仍然需要提供 K
,所以在调用时必须使用虚假类型来实例化 LenOf
:
LenOf[string, int]("foo")
// ^ 实际上是无用的
而且你也不能利用参数的类型推断。
总之:只需使用 len
。设计你的通用函数以使用支持长度的类型字面量,或者仅将那些你的函数可以合理处理的类型添加到约束中。
英文:
Why? The builtin len
is already "generic".
<hr>
With that said, let's see why defining such a constraint is a bad idea. The Go spec has a paragraph — Length and capacity, that can help:
> If the argument type is a type parameter P, the call len(e)
(or cap(e)
respectively) must be valid for each type in P's type set. The result is the length (or capacity, respectively) of the argument whose type corresponds to the type argument with which P was instantiated.
The issues with writing an all-encompassing constraint for "measurable" types are:
- it includes arrays
[N]T
, where the array length is part of the type, so your constraint would have to specify all possible arrays you want to capture - it includes array pointers
*[N]T
, which you can't easily abstract in a type constraint - it includes maps, which forces you to capture keys
K
and valuesV
, which may or may not be the same asT
. Plus,K
must implementcomparable
.
So you'd have to write something like:
type Measurable[T any, K comparable, V any] interface {
~string | ~[]T | ~map[K]V | ~chan T
}
which notably doesn't include arrays, and doesn't distinctly capture pointer literals, e.g. to match []*int
, you would have to instantiate T
with *int
.
You might simplify V
away:
type Measurable[T any, K comparable] interface {
~string | ~[]T | ~map[K]T | ~chan T
}
The function LenOf
then becomes:
func LenOf[T any, K comparable, M Measurable[T, K]](m M) int {
return len(m)
}
but you still have to supply K
, so you have to instantiate LenOf
at call site with bogus types for the map key:
LenOf[string, int]("foo")
// ^ actually useless
and you can't take advantage of type inference with the argument either.
In conclusion: just use len
. Design your generic functions to work with type literals that support length, or add to the constraint only those types that your functions are reasonably expected to handle.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论