golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
122.98k stars 17.53k forks source link

spec: allow 'any' for 'interface{}' in non-constraint contexts #33232

Closed Lexkane closed 2 years ago

Lexkane commented 5 years ago

Note, 2021-09-02: Now that any is an alias for interface{}, 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.

atdiar commented 3 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.

nadiasvertex commented 3 years ago

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".

nadiasvertex commented 3 years ago

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.

rsc commented 3 years ago

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

gopherbot commented 3 years ago

Change https://golang.org/cl/347296 mentions this issue: all: gofmt -w -r 'interface{} -> any' src

DeedleFake commented 3 years ago

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.

rsc commented 3 years ago

@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.

DeedleFake commented 3 years ago

@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.

bcmills commented 3 years ago

@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.

rsc commented 3 years ago

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:

  1. '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.

  2. '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).

  3. 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.

  4. 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.)

carwyn commented 3 years ago

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.

rsc commented 3 years ago

Based on the discussion above, this proposal seems like a likely accept. — rsc for the proposal review group

ron-wolf commented 3 years ago

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

@bcmills Is this because interfaces are a form of structural typing, while structs use nominative typing?

kokizzu commented 3 years ago

Just like byte is alias of uint8 And rune is alias of int32 Adding one more for readability is fine

billinghamj commented 3 years ago

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

ucirello commented 3 years ago

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

griesemer commented 3 years ago

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.)

rsc commented 3 years ago

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.

rsc commented 2 years ago

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.

makhov commented 2 years ago

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.

dsnet commented 2 years ago

@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).

gopherbot commented 2 years ago

Change https://golang.org/cl/351456 mentions this issue: cmd/compile: allowanyanywhere (as a type)

makhov commented 2 years ago

@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.

dsnet commented 2 years ago

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.

griesemer commented 2 years ago

@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.

makhov commented 2 years ago

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.

fzipp commented 2 years ago

Isn't the final comment period over?

makhov commented 2 years ago

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.

dsnet commented 2 years ago

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:

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.

makhov commented 2 years ago

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 :)

griesemer commented 2 years ago

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.

rsc commented 2 years ago

No change in consensus, so accepted. 🎉 This issue now tracks the work of implementing the proposal. — rsc for the proposal review group

ianlancetaylor commented 2 years ago

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.

gopherbot commented 2 years ago

Change https://golang.org/cl/351731 mentions this issue: test/fixedbugs: adjust test case (fix longtest builders)

makhov commented 2 years ago

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?

griesemer commented 2 years ago

@makhov To repeat what @ianlancetaylor already said: If you want to discuss why that is, please do so on golang-nuts, not here. Thanks.

ianlancetaylor commented 2 years ago

Actually, this is done, sorry.

rsc commented 2 years ago

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.

gopherbot commented 2 years ago

Change https://golang.org/cl/369954 mentions this issue: go/analysis/passes/stdmethods: recognize any as alias for interface{}, for errors.As check

gopherbot commented 2 years ago

Change https://golang.org/cl/382248 mentions this issue: compiler: accept "any" as an alias for "interface{}"