Closed Lexkane closed 2 years ago
Semantic subtyping is a variant of structural subtyping that has been implemented in an O-Caml based language. They use a set theoretic interpretation of types.
They use a SMT solver to type check one of their language C-Duce if I remember well.
It's basically constraint type checking which is similar to having types as sets of propositions.
They even have the luxury of having variance bolted in the type system and make it still decidable if I still do remember well. Although I do not know how slow the type checking is because of it.
The warning is about a seemingly unrelated issue but that will apply here. Can be found in "A gentle introduction to semantic subtyping" page 200. (available via a websearch) This is about breaking the circularity problem in presence of arrow types (functions basically). This is actually a similar issue to what I am hinting at. Namely, the constraints on types are different from the constraints of types. In other terms, do we have constraint sets first then types, or type-derived constraint sets?
Especially for interfaces. And when solving, it needs to be taken into account for soundness.
They are the only mechanism that exists for accomplishing certain tasks. If you look through the go standard library you will see them used over and over.
As you may notice, I said exact same thing after sentence you quoted. These usages limited by basically libraries for data serialization or some sort of containers and wrappers.
Which means that new users especially need to understand them. Your argument that their use is limited and therefore not worthy of simplification does not hold up.
For example, what is more immediately clear to a new user reading the following (real) prototype from the library
Neither. You still need to explain what to pass in documentation. This case is also covered by "type parameters reduce usage of
interface{}
". So I don't get what's your point here. And you still don't pass value of "any type", you pass wrapped value of concrete type so it's probably important to have this information right on hand.
The point is that inteface{} is a needlessly esoteric spelling of any type. We should just say "any".
And when you have some value with interface{} type you explicitly state that "value of that type will be wrapped in a struct".
That's not true.
What do you mean? This is struct that describes interfaces in Go runtime:
type iface struct { tab *itab data unsafe.Pointer }
I think I have trouble understanding your English. Certainly if you pass a type into a function that takes a value interface what you say is true. My point is that just because something acts as a receiver doesn't mean that the value has to be wrapped up into yet another object. Also, what you point to is an implementation detail, not a semantic requirement of the language. The machinery of the implementation can be changed without necessarily affecting the semantics of the language.
This proposal has been added to the active column of the proposals project and will now be reviewed at the weekly proposal review meetings. — rsc for the proposal review group
Change https://golang.org/cl/347296 mentions this issue: all: gofmt -w -r 'interface{} -> any' src
I thought it was worth mentioning that interface{}
has a natural pairing with struct{}
, and both function very similarly to Any
and Unit
in Kotlin. Any
is the equivalent of Java's Object
, the automatic top of the class hierarchy, allowing any type to be assigned to a variable of type Any
by way of subtype polymorphism, thus acting fairly similarly to interface{}
in Go.
Similarly, Unit
does the opposite, allowing only the value Unit
to be assigned to it and thus carrying no information whatsoever, very similarly to struct{}
and other, less commonly used empty types in Go.
Something like unit
would have a quirk in Go, of course, as unit
could only be a type, not also the value for that type, if it was just an alias for struct{}
. The value would instead have to be unit{}
, which is kind of odd.
@DeedleFake, unit would be a separate proposal (and exceedingly unlikely to be accepted). This is only about increasing the visibility of the now-already-existing any
.
@rsc I know. I'm not proposing it. Just mentioning prior art and something generally related.
Personally, I'm not in favor of any
, and one of the reasons for that is that unit
, the natural counterpart, would be weird. If any
hadn't been accepted for use in type constraints, I would have been very against its inclusion as a general-use alias, but because it has been, and only because it has been, it seems to make some sense. That doesn't sit well with me.
@DeedleFake, struct{}
is not the counterpart to any
. There are infinitely many possible unit types (imagine an unexported type unit struct{}
in each of infinitely many packages), whereas there is only one any
type: even if you define a separate any
type in each of infinitely many packages, they are all interchangeable and mutually-assignable.
The type-theoretic counterpart to any
is the “bottom” type (⊥), which is the type of an expression that cannot evaluate to a value (e.g. one that must loop forever, panic
, or call runtime.Goexit
). There is no well-defined bottom type in Go's type system today, and I would be shocked if one were ever added, so I think its omission here is perfectly natural.
I posted https://golang.org/cl/347296 to show what a blind replacement would do in the main Go repo. (For backwards compatibility, we're unlikely to do much better than that.) Overall, the code is shorter and I think a bit clearer, but definitely feels foreign. That foreign feeling would dissipate pretty quickly. (I remember the same feeling when we started capitalizing for export and when os.Error became error. You adapt quickly, and the old code starts to look foreign instead.)
I don't believe the way the code looks is dispositive one way or another: it looks fine before, and it looks fine after. Instead I think we should be considering the larger effects of the change.
As I understand it, the main reason to decline this proposal, or put it back on hold, and leave 'any' only useful for constraints in Go 1.18, is to be cautious and move slowly. In general I think that's often good advice, but it's not a hard rule.
Here are some reasons for accepting the proposal in Go 1.18, which I personally find persuasive in aggregate:
'any' being only for constraints is a detail that will be in every writeup of generics - books, blog posts, and so on. If we think we are likely to allow it eventually, it makes sense to allow it from the start and avoid invalidating all that written material.
'any' being only for constraints is an unexpected cut-out that reduces generality and orthogonality of concepts. It's easy to say "let's just wait and see", but prescribing uses tends to create much more jagged features than full generality. We saw this with type aliases as well (and resisted almost all the proposed cut-outs, thankfully).
If 'any' is allowed in generics but not non-generic code, then it might encourage people to overuse generics simply because 'any' is nicer to write than 'interface{}', when the decision about generics or not should really be made by considering other factors.
If we allow 'any' for ordinary non-generic usage too, then seeing interface{} in code could serve as a kind of signal that the code predates generics and has not yet been reconsidered in the post-generics world. Some code using interface{} should use generics. Other code should continue to use interfaces. Rewriting it one way or another to remove the text 'interface{}' would give people a clear way to see what they'd updated and hadn't. (Of course, some code that might be better with generics must still use interface{} for backwards-compatibility reasons, but it can still be updated to confirm that the decision was considered and made.)
2. 'any' being only for constraints is an unexpected cut-out that reduces generality and orthogonality of concepts.
This also adds to the overhead of those people learning Go for the first time. Differences that feel like they should be the same thing often confuse people that are learning and not aware of the history of the language.
Based on the discussion above, this proposal seems like a likely accept. — rsc for the proposal review group
There are infinitely many possible unit types (imagine an unexported
type unit struct{}
in each of infinitely many packages), whereas there is only oneany
type
@bcmills Is this because interfaces are a form of structural typing, while structs use nominative typing?
Just like byte is alias of uint8 And rune is alias of int32 Adding one more for readability is fine
Would gofmt rewrite interface{}
to any
automatically? I imagine this could be quite helpful in the pursuit of consistency and there being one way to do any given thing
Would gofmt rewrite interface{} to any automatically? I imagine this could be quite helpful in the pursuit of consistency and there being one way to do any given thing
I believe this would be a work better appropriate to go fix
Would gofmt rewrite interface{} to any automatically? I imagine this could be quite helpful in the pursuit of consistency and there being one way to do any given thing
I don't think gofmt
should be rewriting interface{}
to any
. A programmer may choose interface{}
deliberately, for instance because the intent is to add methods eventually. It would be really annoying if gofmt
were to change that code. More generally, gofmt
is in the business of organizing "white space", not rewriting code. (The fact that gofmt
does have some options for rewriting, such as the -s
and -r
flags, is largely historic; we wouldn't be doing this now.)
Note that if you want to do the rewrite, gofmt -r 'interface{} -> any'
works fine.
But gofmt shouldn't do the rewrite unconditionally, nor should check-in scripts, etc force use of any particular -r
option.
We are going to accept this change. Robert will update the compiler. Please do not send CLs updating the main Go tree to use any in new places. We will do it in a single CL ourselves. Thank you.
If I recall correctly there was a promise not to change the language in Go 1. And adding a new keyword is quite a big change.
There are a lot of packages, libraries, and apps that use any
as a type, type alias, or variable name: https://github.com/search?l=Go&q=%22any%22&type=Code
Just assume the situation when your project stops compiling because of some indirect dependency that uses any
.
@makhov: This isn't adding a new keyword, but rather adding a new builtin alias.
Any local declarations of any
will take precedence over the builtin declaration for any
. This would only break programs that were relying on the build failing if the any
identifier was not present (which seems extremely unlikely).
Change https://golang.org/cl/351456 mentions this issue: cmd/compile: allow
anyanywhere (as a type)
@dsnet That's really good, thank you. But do you think it's a good idea to write something like this:
package main
import (
"fmt"
)
func main() {
var byte string
byte = "foo"
fmt.Println(byte)
}
How many people will be involved to support the change that brings nothing? We need changes in our tools, libraries, and applications.
For example, google.golang.org/grpc
package uses any
as a var name. So a huge amount of projects will be affected by the change.
Generally, it's not a good idea to shadow an identifier (builtin or otherwise), but it's not incorrect either. I don't think grpc
(or any codebase) needs to change any of their usages of any
.
@makhov What @dsnet said. This is a 100% backward-compatible change. any
is a predeclared alias for interface{}
. Regarding your 2nd comment: You could always shoot yourself into the foot and write something like const true = false
. The answer is simple: just don't do that.
Generally, it's not a good idea to shadow an identifier (builtin or otherwise)
Right, but with this change, we turn a good code into an ugly one without any action from the author!
We all speak about simplicity, try to decrease cognitive load, do things in the most straightforward and obvious way, but here we introduce a small change that will make thousands of (or hundreds of thousands) people think and try to separate type alias from a variable reading already written code.
Isn't the final comment period over?
The answer is simple: just don't do that.
@griesemer and I don't. But now looks like someone is shooting in my foot without me doing anything :)
My point here is that the change will introduce more issues than value, at least now.
Right, but with this change, we turn a good code into an ugly one without any action from the author!
That's true of any language or library change, though. For example:
unsafe.Add
will make any occurrence of unsafe.Pointer(uintptr(ptr) + uintptr(len))
ugly.slices.Sort
will make any occurrence of sort.Slice(x, y, func(i, j int) bool { return x[i] < y[i] })
ugly.bytes.Clone
will make any occurrence of []byte(string(b))
ugly.change will introduce more issues than value, at least now.
That's certainly subjective. The 118 thumbs up and the 44 thumbs down seems to suggest that most people believe this has value.
That's true of any language or library change, though. For example:
That's not what I'm talking about, so I'd say your examples are irrelevant. I'm not arguing the transition from interface{}
to any
here (yet). Valid usage of var any
is no longer valid after the change, that's the point.
That's certainly subjective. The 118 thumbs up and the 44 thumbs down seems to suggest that most people believe this has value.
I'm from Russia, don't tell me about democracy :)
Valid use of var any ...
remains valid after this change; that is the point of this change being backward-compatible.
Let's stop this discussion. This issue has been up for > 2 years and there was plenty of time to comment and plenty of discussion on its merits and disadvantages. Thanks.
No change in consensus, so accepted. 🎉 This issue now tracks the work of implementing the proposal. — rsc for the proposal review group
Reopening because this is not yet done.
@makhov This change is completely backward compatible. If you want to discuss why that is, please do so on golang-nuts, not here. Thanks.
Change https://golang.org/cl/351731 mentions this issue: test/fixedbugs: adjust test case (fix longtest builders)
Valid use of var any ... remains valid after this change; that is the point of this change being backward-compatible.
This change is completely backward compatible.
@ianlancetaylor @griesemer Only in the sense that it still will compile. But software engineering is much more than writing code that compiles, right? Again, legit code will become ugly. Making the product of your customers worse is not a good way to go, IMHO.
Also, the issue has LanguageChange
and Go2
labels. Will it be implemented only in Go2?
@makhov To repeat what @ianlancetaylor already said: If you want to discuss why that is, please do so on golang-nuts, not here. Thanks.
Actually, this is done, sorry.
For people who are landing on this issue now, note that the reasoning for acceptance is in https://github.com/golang/go/issues/33232#issuecomment-915333205, which is in the middle of the issue thread and therefore hidden by default.
Change https://golang.org/cl/369954 mentions this issue: go/analysis/passes/stdmethods: recognize any as alias for interface{}, for errors.As check
Change https://golang.org/cl/382248 mentions this issue: compiler: accept "any" as an alias for "interface{}"
Note, 2021-09-02: Now that
any
is an alias forinterface{}
, we have a special case that it can only be used in type parameter constraints. This proposal is now to remove that special case, allowing any, well, anywhere.This CL shows what it would look like if used throughout the Go tree.
- rsc
I would lilke to be able to declare any type with keyword any instead of empty interface, like rune means alias int32. Such feature make language more readable , without polluting declarations with many empty interfaces. Imho any keyword is perfect for such alias.