英文:
Generics mechanism for constraints referring to other type parameters
问题
下面是一个通用的split
函数,它根据输入的大小将一个切片分割成几个相等大小的切片(最后一个分区可能大小不同):
func split[S ~[]T, T any](slc S, size int) []S {
slices := make([]S, 0, len(slc)/size+1)
for len(slc) > 0 {
if size > len(slc) {
size = len(slc)
}
slices = append(slices, slc[:size])
slc = slc[size:]
}
return slices
}
通用类型参数S
的类型为~[]T
,其中T
为any
。这个函数按预期工作。
~
符号用于处理定义的类型,例如:
type X []string
如果在S ~T[]
中没有~
,split
函数将无法处理类型为X
的参数(但可以处理[]string
类型)。
然后是另一个splitAny
函数:
func splitAny[S ~[]any](slc S, size int) []S {
slices := make([]S, 0, len(slc)/size+1)
for len(slc) > 0 {
if size > len(slc) {
size = len(slc)
}
slices = append(slices, slc[:size])
slc = slc[size:]
}
return slices
}
这个函数可以处理[]interface{}
或任何type []interface{}
类型的参数。
我的问题是,编译器在这里采取了什么机制来生成类型安全的代码?为什么这两个split
函数不等价?更一般地说,为什么splitAny
不能处理例如[]string
类型的参数?
英文:
Below is a generic split
function that splits a slice into several equal sized (except maybe the last partition) slices based on input size:
func split[S ~[]T, T any](slc S, size int) []S {
slices := make([]S, 0, len(slc)/size+1)
for len(slc) > 0 {
if size > len(slc) {
size = len(slc)
}
slices = append(slices, slc[:size])
slc = slc[size:]
}
return slices
}
The generic type parameter S
has type ~[]T
where T
is any
. This works as expected.
The ~
is required to handle defined types, for example:
type X []string
Without the ~
in S ~T[]
, split
wouldn't work on an argument of type X
(it would still work on a []string
).
Then there is this other splitAny
function:
func splitAny[S ~[]any](slc S, size int) []S {
slices := make([]S, 0, len(slc)/size+1)
for len(slc) > 0 {
if size > len(slc) {
size = len(slc)
}
slices = append(slices, slc[:size])
slc = slc[size:]
}
return slices
}
This function works on a []interface{}
or anything that is type []interface{}
.
My question is, what exactly is the mechanism that the compiler is taking here to produce type safe code? Why are the two split function not equivalent. More generally, why can't splitAny
work with, for example, a []string
?
答案1
得分: 2
tl;dr — 约束~[]T
是指类型参数,约束~[]any
是指静态类型any
。
S ~[]T
的语法是S interface { ~[]T }
的简写形式,其中约束是一个匿名接口类型。无论哪种方式,T
都指的是一个类型参数的名称,该类型参数具有以下特点:
- 受到
any
的约束 - 在该类型参数列表中可见
关键在于这里使用了any
作为约束,它满足所有类型。如果你使用了一个命名的约束,那么它将成为一个参数化的接口,如下所示:
type Foo[T any] interface {
~[]T
}
然后你需要将函数签名改写为:
func split[S Foo[T], T any](slc S, size int) []S {}
这样或许更容易看出~[]T
中的T
本身就是一个类型参数。就像T
在每个实例化时可以变化一样,S
也可以变化。当T
为string
时,~[]T
就是~[]string
。
在另一种情况下,约束~[]any
并不指代一个类型参数。any
在类型字面量[]any
中作为静态类型使用。如果你使用了一个命名的约束,它将是这样的:
// 不需要类型参数
type Bar interface {
~[]any
// 与~[]interface{}相同
}
然后你需要将函数签名改写为:
func splitAny[S Bar](slc S, size int) []S {}
在后一种情况下,约束没有参数化,显然只能用具有底层类型[]any
的类型来实例化它,不能使用其他类型。
你也可以使用[]interface{}
来实例化它,因为any是
interface{}`的类型别名。
英文:
tl;dr — the constraint ~[]T
refers to a type parameter, the constraint ~[]any
refers to the static type any
.
<hr>
The syntax S ~[]T
is a shorthand notation for S interface { ~[]T }
, where the constraint is an anonymous interface type. Either way, the T
refers to the name of a type parameter which is:
- constrained by
any
- in scope in that type parameter list
The key is that any
is used as a constraint here, which is satisfied by all types. If you used a named constraint instead, that would be a parametrized interface as:
type Foo[T any] interface {
~[]T
}
and you would rewrite the function signature as:
func split[S Foo[T], T any](slc S, size int) []S {}
This perhaps makes it easier to see that the T
in ~[]T
is itself a type parameter. Just like T
can vary at each instantiation, so can S
. When T
is string
, ~[]T
is ~[]string
In the other case, the constraint ~[]any
doesn't refer to a type parameter. any
is used a static type in the type literal []any
. If you used a named constraint instead it would be:
// no type parameter needed
type Bar interface {
~[]any
// same as ~[]interface{}
}
And you would rewrite the function signature as:
func splitAny[S Bar](slc S, size int) []S {}
In this latter case, the constraint isn't parametrized and clearly can only be instantiated with types with underlying type []any
and nothing else.
You can instantiate it also with []interface{}
because any
is a type alias of interface{}
.
答案2
得分: 1
在split()
函数中,类型参数T
是切片的_元素类型_,因此它可以接受具有不同元素类型的切片,例如[]string
、[]int
等。
在splitAny()
函数中,类型参数S
不是元素类型,而是切片类型本身!这意味着传递的切片类型可以是[]any
或具有[]any
作为其底层类型的类型,但元素类型必须是any
(它是interface{}
的别名),不能是其他任何类型,例如不能是string
,因此用于S
的切片类型不能是[]string
。
英文:
In split()
the type parameter T
is the element type of the slice, so it accepts slices of varying element types, such as []string
, []int
etc.
In splitAny()
the type parameter S
is not the element type but the slice type itself! Which means the type of the slice passed can be []any
or a type that has this as its underlying type, but the element type must be any
(which is an alias to interface{}
), and it can't be anything else, it can't be string
for example, so the slice type used for S
can't be []string
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论