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?
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.
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.
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.
Related
I'm looking for a way to declare type compatibility between type parameters in Go generics constraints.
More specifically, I need to say some type T is compatible with another type U. For instance, T is a pointer to a struct that implements the interface U.
Below is a concrete example of what I want to accomplish:
NOTE: Please, do not answer with alternative ways to implement "array prepend". I've only used it as a concrete application of the problem I'm looking to solve. Focusing on the specific example digresses the conversation.
func Prepend[T any](array []T, values ...T) []T {
if len(values) < 1 { return array }
result := make([]T, len(values) + len(array))
copy(result, values)
copy(result[len(values):], array)
return result
}
The above function can be called to append elements of a given type T to an array of the same type, so the code below works just fine:
type Foo struct{ x int }
func (self *Foo) String() string { return fmt.Sprintf("foo#%d", self.x) }
func grow(array []*Foo) []*Foo {
return Prepend(array, &Foo{x: len(array)})
}
If the array type is different than the elements being added (say, an interface implemented by the elements' type), the code fails to compile (as expected) with type *Foo of &Foo{…} does not match inferred type Base for T:
type Base interface { fmt.Stringer }
type Foo struct{ x int }
func (self *Foo) String() string { return fmt.Sprintf("foo#%d", self.x) }
func grow(array []Base) []Base {
return Prepend(array, &Foo{x: len(array)})
}
The intuitive solution to that is to change the type parameters for Prepend so that array and values have different, but compatible types. That's the part I don't know how to express in Go.
For instance, the code below doesn't work (as expected) because the types of array and values are independent of each other. Similar code would work with C++ templates since the compatibility is validated after template instantiation (similar to duck typing). The Go compiler gives out the error invalid argument: arguments to copy result (variable of type []A) and values (variable of type []T) have different element types A and T:
func Prepend[A any, T any](array []A, values ...T) []A {
if len(values) < 1 { return array }
result := make([]A, len(values) + len(array))
copy(result, values)
copy(result[len(values):], array)
return result
}
I've tried making the type T compatible with A with the constraint ~A, but Go doesn't like a type parameter used as type of a constraint, giving out the error type in term ~A cannot be a type parameter:
func Prepend[A any, T ~A](array []A, values ...T) []A {
What's the proper way to declare this type compatibility as generics constraints without resorting to reflection?
This is a limitation of Go's type parameter inference, which is the system that tries to automatically insert type parameters in cases where you don't define them explicitly. Try adding in the type parameter explicitly, and you'll see that it works. For example:
// This works.
func grow(array []Base) []Base {
return Prepend[Base](array, &Foo{x: len(array)})
}
You can also try explicitly converting the *Foo value to a Base interface. For example:
// This works too.
func grow(array []Base) []Base {
return Prepend(array, Base(&Foo{x: len(array)}))
}
Explanation
First, you should bear in mind that the "proper" use of type parameters is to always include them explicitly. The option to omit the type parameter list is considered a "nice to have", but not intended to cover all use cases.
From the blog post An Introduction To Generics:
Type inference in practice
The exact details of how type inference works are complicated, but using it is not: type inference either succeeds or fails. If it succeeds, type arguments can be omitted, and calling generic functions looks no different than calling ordinary functions. If type inference fails, the compiler will give an error message, and in those cases we can just provide the necessary type arguments.
In adding type inference to the language we’ve tried to strike a balance between inference power and complexity. We want to ensure that when the compiler infers types, those types are never surprising. We’ve tried to be careful to err on the side of failing to infer a type rather than on the side of inferring the wrong type. We probably have not gotten it entirely right, and we may continue to refine it in future releases. The effect will be that more programs can be written without explicit type arguments. Programs that don’t need type arguments today won’t need them tomorrow either.
In other words, type inference may improve over time, but you should expect it to be limited.
In this case:
// This works.
func grow(array []*Foo) []*Foo {
return Prepend(array, &Foo{x: len(array)})
}
It is relatively simple for the compiler to match that the argument types of []*Foo and *Foo match the pattern []T and ...T by substitutingT = *Foo.
So why does the plain solution you gave first not work?
// Why does this not work?
func grow(array []Base) []Base {
return Prepend(array, &Foo{x: len(array)})
}
To make []Base and *Foo match the pattern []T and ...T, just substituting T = *Foo or T = Base provides no apparent match. You have to apply the rule that *Foo is assignable to the type Base to see that T = Base works. Apparently the inference system doesn't go the extra mile to try to figure that out, so it fails here.
As generics have been released in Go 1.18 pretty recently, I've started learning them. I generally get the concept, because I have some Java experience from the past. But I don't get some implementation specifics.
For instance: when it's more suitable to use any instead of interface{}? Here's an example:
func printInterface(foo interface{}) {
fmt.Printf("%v\n", foo)
}
func printAny[T any](foo T) {
fmt.Printf("%v\n", foo)
}
func (suite *TestSuite) TestString() {
printInterface("foo")
printAny("foo")
}
Both implementations work. However, if I try to print nil with any-version, I'll get a compile-time error:
cannot infer T.
https://go.dev/play/p/0gmU4rhhaOP
And I won't get this error if I try to print nil with interface{}-version.
So what's the use-case for any? When and which benefits does it bring, compared to simply using interface{}?
I'm asking to provide a specific example, where one implementation is objectively more suitable than another and/or where there is a specific benefit that can be evaluated.
Beside any and interface{} being type aliases — hence, equivalent in usage —, there is a practical difference between any as type parameter and any as regular function argument, as in your example.
The difference is that in printAny[T any](foo T) the type of foo is not any/interface{}, but it's T. And T after instantiation is a concrete type, that may or may not be an interface itself. You can then only pass arguments to an instantiated printAny that can be assigned to that concrete type.
How this impacts your code is most evident with multiple arguments. If we change the function signatures a bit:
func printInterface(foo, bar any) {
fmt.Println(foo, bar)
}
func printAny[T any](foo, bar T) {
fmt.Println(foo, bar)
}
After instantiation:
the function printAny accepts any two arguments of the same type — whichever is used to instantiate T
printInterface, which is equivalent to printInterface(foo, bar interface{}) can still accept two arguments of different types, since both would be individually assignable to any/interface{}.
printInterface(12.5, 0.1) // ok
printInterface(12.5, "blah") // ok, int and string individually assignable to any
printAny(10, 20) // ok, T inferred to int, 20 assignable to int
printAny(10, "k") // compiler error, T inferred to int, "k" not assignable to int
printAny[any](10, "k") // ok, T explicitly instantiated to any, int and string assignable to any
printAny(nil, nil) // compiler error, no way to infer T
printAny[any](nil, nil) // ok, T explicitly instantiated to any, nil assignable to any
A playground: https://go.dev/play/p/pDjP986cj96
Note: the generic version cannot be called with nil without explicit type arguments simply because nil alone doesn't carry type information, so the compiler can't infer T. However nil can be normally assigned to variables of interface type.
any is an alias for interface{}. Spec: Interface types:
For convenience, the predeclared type any is an alias for the empty interface.
Since it is an alias, it doesn't matter which one you use. They are one and the same. They are interchangeable. You can replace one with the other, the code will mean the same.
any is shorter and clearer, but only works from Go 1.18.
Since they are interchangeable, this also works:
func printInterface(foo any) {
fmt.Printf("%v\n", foo)
}
The reason why printAny() doesn't work is due to it being a generic function with a type parameter. To use it, it must be instantiated (its type parameter must be assigned with a known type). Trying to call it with nil carries no type information, so instantiation cannot happen, type inference won't work.
If you call it with a nil value that carries type info, it'll work, or if you specify the type param explicitly (try it on the Go Playground):
printAny((*int)(nil))
printAny[*int](nil)
// Or
var r io.Reader
printAny(r)
And as said, any is interchangeable with interface{}, so you'll have the same code if you swap both occurrences (try this one on the Go Playground):
func printInterface(foo any) {
fmt.Printf("%v\n", foo)
}
func printAny[T interface{}](foo T) {
fmt.Printf("%v\n", foo)
}
Your issue is not related to the usage of any/interface{} — whose difference is purely cosmetic — but it is type inference. As you can see from this playground, if you instantiate your function with an explicit type, like printAny[any](nil) it will work.
If you have a function with generic type you need to specify the types. However the go compiler is very smart and can infer some types for you. But nil alone is impossible to infer.
In Visual Studio Code, the auto-complete tool (which I presume is gopls?) gives the following template:
m.Range(func(key, value any) bool {
})
where m is a sync.Map. the type any is not recognized, but is put there.
What is any? Can I put the type I want and hope Go 1.18 to do implicit type conversion for me? For example:
m.Range(func(k, v string) { ... })
which will give k, v as string inside the callback, without having to do type cast myself?
any is a new predeclared identifier and a type alias of interface{}.
It comes from issue 49884, CL 368254 and commit 2580d0e.
The issue mentions about interface{}/any:
It's not a special design, but a logical consequence of Go's type declaration syntax.
You can use anonymous interfaces with more than zero methods:
func f(a interface{Foo(); Bar()}) {
a.Foo()
a.Bar()
}
Analogous to how you can use anonymous structs anywhere a type is expected:
func f(a struct{Foo int; Bar string}) {
fmt.Println(a.Foo)
fmt.Println(a.Bar)
}
An empty interface just happens to match all types because all types have at least zero methods.
Removing interface{} would mean removing all interface functionality from the language if you want to stay consistent / don't want to introduce a special case.
I am a big fan of Golang, and very pleased to how the syntax of Go is designed. As a part of syntax philosophy, we have a rule as following: omit the things (keywords, characters etc.) if they are not needed actually.
For that reason instead of writing redundant colons:
for ; sum < 1000; {
sum += sum
}
You allowed to simply put:
for sum < 1000 {
sum += sum
}
notice how we omitted redundant semicolons
And there are lots of other cases where syntax is gratefully simplified.
But what about struct when we define type?
type Person struct {
name string
}
Why do we need to put struct keyword here?
Keywords are to determine intention, to clarify the exact choice of available options so a compiler knows how to do his job properly.
Will it be unclear and ambiguous if we simply put:
type Person {
name string
}
??
I believe there is a meaning for struct in the examples above
because compiler fails when type defined without struct keyword.
Please, explain me (and provide links) what else we can use instead of struct when we define some type.
Please, list available options from which we want to clarify to a compiler that things in curly brackets after type name are exactly parts of a struct and not something else (what else?).
Thanks.
It's not redundant. You can make types from existing types:
type MyType int
type MyType string
Or interfaces:
type Stringer interface {
String() string
}
This is covered in the Go tour and in the spec.
Types (may) not only appear in type declarations, but in countless other places, for example in function declarations.
Structs may be "used" anonymously, without creating a named type for them. For example, the following declaration is valid:
func GetPoint() struct{ x, y int } {
return struct{ x, y int }{1, 2}
}
Without having to use the struct keyword, a parsing ambiguity would arise in multiple uses. Let's say we want to create a function which returns an empty struct:
func GetEmpty() struct{} {
return struct{}{}
}
How would this look like without the struct keyword?
func GetEmpty2() {} {
return {}{}
}
Now if you're the compiler, what would you make out of this? Is this a function with the same signature as GetEmpty()? Or is this a function without a return value and an empty body (func GetEmpty2() {}) followed by a block which contains a return statement? The return statement would be another ambiguity, as it may return nothing which is followed by 2 empty blocks, or it may return an empty struct value which is followed by an empty block...
Now to avoid parsing ambiguity, we have to use the struct keyword when specifying struct types elsewhere (outside of type declarations), then why make it optional or disallow it in type declarations?
I think a consistent syntax is more important than grabbing all chances to reduce the language (syntax) to the minimum possible. That hurts readability big time. The for loop example you mentioned is not really a simplification, but rather the usage of different forms of the for loop.
I came a cross a piece of code that used .(string) method. Not knowing what this is called I had difficulties searching for it.
Here is my try to understand it:
package main
import "fmt"
import "reflect"
func main(){
var b interface{}
b = "silly"
fmt.Println(reflect.TypeOf(b.(string))) // we know that b
// is a string
// at compile time
fmt.Println(reflect.TypeOf(b)) // we do not
}
Result:
string
string
However, I think that reflect.TypeOf takes place at run time, while .(string) would tell the compiler that b is indeed a string, and this could be used to tell the compiler that a variable is of certain type. Is my understanding right?
goplayground
b.(string) is called a type assertion. As written in Effective Go:
A type assertion takes an interface value and extracts from it a value of the specified explicit type.
So, yes, the value you get from a type assertion is not an interface value, but is of the explicit type. You can also test if the type assertion was successful by adding an untyped boolean value:
s, ok := b.(string) // s is of type string
if !ok {
// b did not contain a value of type string!
}
Edit:
To explain further to clear out any possible misunderstanding:
A type assertion doesn't "tell Go that b is a string" as you suggested. What it does is that it will, in run time, try to extract a string from b, and panic if b contains some other type (unless assigning the optional bool value).
The value that you get from the assertion will indeed be of type string, allowing you to do things like slicing (you cannot slice an interface value) or checking its len.
The previous answer is correct. But I submit this as more like what happens in practice. The .(type) syntax is usually used with type names in the cases of a switch. In this example, I (integer expr), B (bool expr), and Bop (binary op) are type names.
func commute (e Expr) (r Expr, d bool) {
switch exp:= e.(type) {
case I: r,d = exp,false
case B: r,d = exp,false
case Bop: r,d = Bop{exp.op, exp.right, exp.left},true
default: r,d = e,false
}
return
}
This isn't unsafe like a C cast, because inside the case statement you are guaranteed to have the matching type. I see this quite a bit when reading a channel, where the type of the channel is an interface that all the cases implement.