在Go泛型中,什么时候不需要使用波浪符(tilde)?

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

When is the tilde not necessary in Go generics?

问题

使用Golang的新泛型,我们有了波浪线操作符~,它将匹配底层类型。在什么情况下是有效的匹配底层类型?我试图理解为什么波浪线的当前行为不是默认行为。支持两者似乎是不必要的。

例如,为什么要写

interface { int }

而不是

interface { ~int }

对于你来说,写一个如此严格的方法,以至于不能接受像

type MyInt int

这样的东西,有什么好处?为什么波浪线的行为不是默认的,从而语言不需要另一个操作符?

英文:

With Golangs new generics we have the tilde operator ~ which will match the underlying type. In what case is it valid to NOT match the underlying type? I'm trying to understand why the current behavior with the tilde is not the default behavior. It seems unnecessary to support both.

For example, why would you write

interface { int }

and not

interface { ~int }

What benefit to you would it be to write a method that is so strict that it could not accept something like

type MyInt int

Why is the tilde behavior not the default, and thus the language would not require another operator?

答案1

得分: 5

不使用~运算符意味着你只接受列出的确切类型。为什么这很重要呢?

你可能希望使用确切类型的值来设置其他变量,否则就需要进行类型转换。因为俗话说得好:“新类型,新方法集”。具有相同底层类型的新类型具有它们自己的方法集。

你可能希望保留值的“原始”行为,如果它具有不同的方法集,则可能会发生变化。

例如,假设你想以以下方式打印数字:

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 }

你仍然可以使用类型转换将类型为MyInt的值传递给foo()

foo(1)
x := MyInt(1)
foo(int(x))

输出将是foo()想要的,而不是MyInt想要的(在Go Playground上试一下):

1
1

是的,如果foo()本身进行转换也是可能的,但这样明确地说明了你想要一个纯粹的int,具有int的行为,而不是具有不同的自定义行为的int

英文:

Not using the ~ operator means you only accept the listed exact types. Why should this matter?

You may want to use the values of the exact types to set to other variables and else type conversion would be required. And because the saying goes "new type, new method set". New types having the same underlying type have their own method sets.

You may want the "original" behavior of the value, which may change if it has a different method set.

For example, let's say you want to print the number like this:

type Num interface{ ~int }

func foo[T Num](v T) {
	fmt.Println(v)
}

If MyInt has a String() string method:

type MyInt int

func (m MyInt) String() string { return "bar" }

The output might not be what foo() would want, because the fmt package checks if a printed value has a String() string method, and if so, it is called to acquire its string representation:

foo(1)
foo(MyInt(1))

This will output (try it on the Go Playground):

1
bar

If you only allow int:

type Num interface{ int }

You can still call foo() with a value of type MyInt, using a type conversion:

foo(1)
x := MyInt(1)
foo(int(x))

And output will be what foo() wants, not what MyInt would want (try this one on the Go Playground):

1
1

Yes, this would also be possible if foo() itself would do the conversion, but this clearly documents you want a pure int, with int's behavior, and not something that is an int with a different, custom behavior.

答案2

得分: 0

为什么波浪符行为不是默认的?

因为这样会让人困惑,并且在写一个像 func Foo[T int](v T) 这样接受非 int 类型参数的函数时,语义上是不正确的。在接口约束中,int 的含义将与其他地方不同。(更多讨论)

仅当约束中的类型集合的基数为1时,使用类型参数是无意义的。如果约束的类型集合的基数为1,应该直接移除类型参数。

一个函数如下:

func Foo[T int](v T)

只能实例化为 int,所以可以(也应该!)用普通参数来简单地写成

func Foo(v int)

当类型集合的基数为N时,其中包括单个波浪符类型和联合类型,基本上不可能编写穷尽类型切换,因为在case语句中使用~是不允许的(尚未允许?):

func Foo[T ~int | ~string](v T) {
    switch t := any(v).(type) {
        case int: // ok
        case string: // ok

        // 如何匹配其他可能的类型?
    }
} 

在这种特殊情况下,只有当约束包含确切的类型时,才能编写穷尽的类型切换:

func Foo[T int | string](v T) {
    switch t := any(v).(type) {
        case int: // ok
        case string: // ok

        default:
            panic("should not occur")
    }
}

在实践中,这种情况不会经常出现:如果你发现自己在类型参数上进行切换,你应该问问自己是否真的需要泛型函数。然而,在设计代码时,这种用例是相关的。

英文:

> Why is the tilde behavior not the default

Because it would be confusing and semantically unsound to write a function like func Foo[T int](v T) that accepts type parameters that are not int. Then the meaning of int in interface constraints would not be the same as everywhere else. (More on this discussion)

<hr>

> What benefit to you would it be to write a method that is so strict [...]

Indeed if the constraint includes only one exact type, using type parameters is moot. If the type set of the constraint has cardinality 1, you should just remove the type parameter.

A function like:

func Foo[T int](v T)

can only ever be instantiated with exactly int, so it can (and should!) be simply written with regular arguments:

func Foo(v int)

<hr>

When the type set cardinality is N, which includes single tilde types, but also unions, makes it basically impossible to write exhaustive type switch, since using ~ in case statements is not allowed (yet?):

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? 
    }
} 

In this particular case, an exhaustive type switch can be written only if the constraint includes exact types:

func Foo[T int | string](v T) {
    switch t := any(v).(type) {
        case int: // ok
        case string: // ok

        default:
            panic(&quot;should not occur&quot;)
    }
}

This should not arise frequently in practice: if you find yourself switching on the type parameter, you should ask yourself if the function really needs to be generic. However the use case is relevant when designing your code.

答案3

得分: -1

为什么波浪符行为不是默认的,从而语言不需要另一个运算符?

因为如果近似是无条件的默认值,你就无法表达多态函数需要一个 int 而不是 MyInt 的事实。然后你将不得不引入一个像 strict 的运算符,写成 %int。没有任何收益。

英文:

> Why is the tilde behavior not the default, and thus the language would not require another operator?

Because if the approximation would be the default unconditionally you could not express the fact that your polymorphic function requires an int and not a MyInt. You would then have to introduce an operator like strict and write %int. Nothing gained.

huangapple
  • 本文由 发表于 2022年5月12日 10:08:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/72209375.html
匿名

发表评论

匿名网友

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

确定