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?
CodePudding user response:
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.
CodePudding user response:
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.
CodePudding user response:
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)
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)
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("should not occur")
}
}
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.