Go 泛型什么时候不需要波浪号?
When is the tilde not necessary in Go generics?
对于 Golang 的新泛型,我们有波浪号运算符 ~ ,它将匹配基础类型。 NOT 匹配基础类型在什么情况下有效?我试图理解为什么波浪号的当前行为不是默认行为。好像没必要同时支持。
比如,你为什么要写
interface { int }
而不是
interface { ~int }
编写一个严格到不能接受类似
的方法对你有什么好处
type MyInt int
为什么波浪号行为不是默认行为,因此该语言不需要其他运算符?
Why is the tilde behavior not the default, and thus the language would not require another operator?
因为如果近似值是无条件的默认值,则您无法表达您的多态函数需要 int
而不是 MyInt
这一事实。然后,您将不得不引入一个运算符,如 strict 并编写 %int
。一无所获。
不使用 ~
运算符意味着您只接受列出的确切类型。为什么这很重要?
您可能希望使用确切类型的值来设置其他变量,否则将需要类型转换。又因为俗话说“新类型,新方法集”。具有相同基础类型的新类型有自己的 method sets.
您可能需要该值的“原始”行为,如果设置了不同的方法,该行为可能会发生变化。
例如,假设您想像这样打印数字:
type Num interface{ ~int }
func foo[T Num](v T) {
fmt.Println(v)
}
如果MyInt
有一个String() string
方法:
type MyInt int
func (m MyInt) String() string { return "bar" }
输出可能不是 foo()
想要的,因为 fmt
包检查打印值是否有 String() string
方法,如果有,它会被调用以获取它的 string
表示:
foo(1)
foo(MyInt(1))
这将输出(在 Go Playground 上尝试):
1
bar
如果只允许 int
:
type Num interface{ int }
您仍然可以调用 foo()
并使用类型 MyInt
的值,使用类型 conversion:
foo(1)
x := MyInt(1)
foo(int(x))
并且输出将是 foo()
想要的,而不是 MyInt
想要的(在 Go Playground 上试试这个):
1
1
是的,如果 foo()
本身进行转换,这也是可能的,但这清楚地表明您想要一个纯 int
,具有 int
的行为,而不是int
具有不同的自定义行为。
Why is the tilde behavior not the default
因为编写像 func Foo[T int](v T)
这样接受非 int
类型参数的函数会造成混淆并且在语义上不合理。那么接口约束中int
的含义就不会和其他地方一样了。 (More on this discussion)
What benefit to you would it be to write a method that is so strict [...]
确实,如果约束仅包含 一个确切的 类型,则使用类型参数没有实际意义。如果约束的类型集的基数为 1,您应该只删除类型参数。
函数如:
func Foo[T int](v T)
只能用int
实例化,所以它可以(而且应该!)简单地用常规参数编写:
func Foo(v int)
当类型集基数为 N 时,它包括单波浪号类型,但也包括联合,这使得编写 exhaustive 类型开关基本上是不可能的,因为使用 ~
在 case
语句中不允许(还?):
func Foo[T ~int | ~string](v T) {
switch t := any(v).(type) {
case int: // ok
case string: // ok
// how to match other possible types then?
}
}
在这种特殊情况下,只有在约束包含确切类型时才能编写详尽类型开关:
func Foo[T int | string](v T) {
switch t := any(v).(type) {
case int: // ok
case string: // ok
default:
panic("should not occur")
}
}
这在实践中不应该经常出现:如果你发现自己打开了类型参数,你应该问问自己函数是否真的需要泛型。但是,在设计代码时用例是相关的。
对于 Golang 的新泛型,我们有波浪号运算符 ~ ,它将匹配基础类型。 NOT 匹配基础类型在什么情况下有效?我试图理解为什么波浪号的当前行为不是默认行为。好像没必要同时支持。
比如,你为什么要写
interface { int }
而不是
interface { ~int }
编写一个严格到不能接受类似
的方法对你有什么好处type MyInt int
为什么波浪号行为不是默认行为,因此该语言不需要其他运算符?
Why is the tilde behavior not the default, and thus the language would not require another operator?
因为如果近似值是无条件的默认值,则您无法表达您的多态函数需要 int
而不是 MyInt
这一事实。然后,您将不得不引入一个运算符,如 strict 并编写 %int
。一无所获。
不使用 ~
运算符意味着您只接受列出的确切类型。为什么这很重要?
您可能希望使用确切类型的值来设置其他变量,否则将需要类型转换。又因为俗话说“新类型,新方法集”。具有相同基础类型的新类型有自己的 method sets.
您可能需要该值的“原始”行为,如果设置了不同的方法,该行为可能会发生变化。
例如,假设您想像这样打印数字:
type Num interface{ ~int }
func foo[T Num](v T) {
fmt.Println(v)
}
如果MyInt
有一个String() string
方法:
type MyInt int
func (m MyInt) String() string { return "bar" }
输出可能不是 foo()
想要的,因为 fmt
包检查打印值是否有 String() string
方法,如果有,它会被调用以获取它的 string
表示:
foo(1)
foo(MyInt(1))
这将输出(在 Go Playground 上尝试):
1
bar
如果只允许 int
:
type Num interface{ int }
您仍然可以调用 foo()
并使用类型 MyInt
的值,使用类型 conversion:
foo(1)
x := MyInt(1)
foo(int(x))
并且输出将是 foo()
想要的,而不是 MyInt
想要的(在 Go Playground 上试试这个):
1
1
是的,如果 foo()
本身进行转换,这也是可能的,但这清楚地表明您想要一个纯 int
,具有 int
的行为,而不是int
具有不同的自定义行为。
Why is the tilde behavior not the default
因为编写像 func Foo[T int](v T)
这样接受非 int
类型参数的函数会造成混淆并且在语义上不合理。那么接口约束中int
的含义就不会和其他地方一样了。 (More on this discussion)
What benefit to you would it be to write a method that is so strict [...]
确实,如果约束仅包含 一个确切的 类型,则使用类型参数没有实际意义。如果约束的类型集的基数为 1,您应该只删除类型参数。
函数如:
func Foo[T int](v T)
只能用int
实例化,所以它可以(而且应该!)简单地用常规参数编写:
func Foo(v int)
当类型集基数为 N 时,它包括单波浪号类型,但也包括联合,这使得编写 exhaustive 类型开关基本上是不可能的,因为使用 ~
在 case
语句中不允许(还?):
func Foo[T ~int | ~string](v T) {
switch t := any(v).(type) {
case int: // ok
case string: // ok
// how to match other possible types then?
}
}
在这种特殊情况下,只有在约束包含确切类型时才能编写详尽类型开关:
func Foo[T int | string](v T) {
switch t := any(v).(type) {
case int: // ok
case string: // ok
default:
panic("should not occur")
}
}
这在实践中不应该经常出现:如果你发现自己打开了类型参数,你应该问问自己函数是否真的需要泛型。但是,在设计代码时用例是相关的。