泛型机制用于引用其他类型参数的约束。

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

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,其中Tany。这个函数按预期工作。

~符号用于处理定义的类型,例如:

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也可以变化。当Tstring时,~[]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.

huangapple
  • 本文由 发表于 2022年6月16日 21:38:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/72646826.html
匿名

发表评论

匿名网友

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

确定