New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: spec: add typed enum support #19814
Comments
@derekparker there's a discussion for making a Go2 proposal in #19412 |
I read through that earlier today, but that seemed more focused on valid types, where this is focused on valid type values. Maybe this is a subset of that proposal, but also is a less far-reaching change to the type system that could be put into Go today. |
enums are a special case of sum types where all the types are the same and there's a value associated to each by a method. More to type, surely, but same effect. Regardless, it would be one or the other, sum types cover more ground, and even sum types are unlikely. Nothing's happening until Go2 because of the Go1 compatibility agreement, in any case, since these proposals would, at the very least, require a new keyword, should any of them be accepted |
Fair enough, but neither of these proposals is breaking the compatibility agreement. There was an opinion expressed that sum types were "too big" to add to Go1. If that's the case, then this proposal is a valuable middle ground that could be a stepping stone to full sum types in Go2. |
They both require a new keyword which would break valid Go1 code using that as an identifier |
I think that could be worked around |
A new language feature needs compelling use cases. All language features are useful, or nobody would propose them; the question is: are they useful enough to justify complicating the language and requiring everyone to learn the new concepts? What are the compelling use cases here? How will people use these? For example, would people expect to be able to iterate over the set of valid enum values, and if so how would they do that? Does this proposal do more than let you avoid adding default cases to some switches? |
Here's the idiomatic way of writing enumerations in current Go: type SearchRequest int
const (
Universal SearchRequest = iota
Web
Images
Local
News
Products
Video
) This has the advantage that it's easy to create flags that can be OR:ed (using operator type SearchRequest int
const (
Universal SearchRequest = 1 << iota
Web
Images
Local
News
Products
Video
) I can't see that introducing a keyword |
@md2perpe that isn't enums.
package main
import (
"fmt"
)
func main() {
type SearchRequest int
const (
Universal SearchRequest = iota
Web
)
const (
Another SearchRequest = iota
Foo
)
fmt.Println("Should be false: ", (Web == Foo))
// Prints: "Should be false: true"
} I totally agree with @derekperkins that Go needs some enum as first class citizen. How that would look like, I'm not sure, but I suspect it could be done without breaking the Go 1 glass house. |
@md2perpe
As soon as you need to represent a string or another type, which is very common for external flags, This isn't just an convenience issue to make code shorter. This is a proposal that will allow for data integrity in a way that is not enforceable by the language today. |
I think that is a solid use case, as mentioned by @bep. I think the iteration would look like a standard Go loop, and I think they would loop in the order that they were defined.
|
If Go were to add anything more than iota, at that point why not go for algebraic data types? |
By extension of ordering according to the definition order, and following the example of protobuf, I think that the default value of the field would be the first defined field. |
@bep Not as convenient, but you can get all these properties:
|
I don't think compile-time checked enums are a good idea. I believe go pretty much has this right right now. My reasoning is
Overall, just a huge -1 for me. Not only doesn't it add anything; it actively hurts. |
I think current enum implementation in Go is very straightforward and provides enough compilation time checks. I actually expect some kind of Rust enums with basic pattern matching, but it possibly breaks Go1 guaranties. |
Since enums are a special case of sum types and the common wisdom is that we should use interfaces to simulate sum types the answer is clearly https://play.golang.org/p/1BvOakvbj2 (if it's not clear: yes, that is a joke—in classic programmer fashion, I'm off by one). In all seriousness, for the features discussed in this thread, some extra tooling would be useful. Like the stringer tool, a "ranger" tool could generate the equivalent of the Something could generate {Binary,Text}{Marshaler,Unmarshaler} implementations to make them easier to send over the wire. I'm sure there are a lot of little things like this that would be quite useful on occasion. There are some vetting/linter tools for exhaustiveness checking of sum types simulated with interfaces. No reason there couldn't be ones for iota enums that tell you when cases are missed or invalid untyped constants are used (maybe it should just report anything other than 0?). There's certainly room for improvement on that front even without language changes. |
Enums would complement the already established type system. As the many examples in this issue have shown, the building blocks for enums is already present. Just as channels are high level abstractions build on more primitives types, enums should be built in the same manner. Humans are arrogant, clumsy, and forgetful, mechanisms like enums help human programmers make less programming errors. |
@bep I have to disagree with all three of your points. Go idiomatic enums strongly resemble C enums, which do not have any iteration of valid values, do not have any automatic conversion to strings, and do not have necessarily distinct identity. Iteration is nice to have, but in most cases if you want iteration, it is fine to define constants for the first and last values. You can even do so in a way that does not require updating when you add new values, since Automatic conversion to string is only a small value: especially in this proposal, the string values need to be written to correspond to the int values, so there is little to be gained over explicitly writing an array of string values yourself. In an alternate proposal, it could be worth more, but there are downsides to forcing variable names to correspond to string representations as well. Finally, distinct identity I'm not even sure is a useful feature at all. Enums are not sum types as in, say, Haskell. They are named numbers. Using enums as flag values, for instance, is common. For instance, you can have |
@alercah, please don't pull this Go was built to be a better C/C++ or a less verbose Java, so comparing it to the latter would make more sense. And Java does have a built-in And, while you may disagree with the "much more powerful part", the Java I can appreciate the argument that Go is leaner, simpler etc., and that some compromise must be taken to keep it this way, and I have seen some hacky workarounds in this thread that kind of works, but a set of |
Enumerations and automatic string conversions are good candidates for the 'go generate' feature. We have some solutions already. Java enums are something in the middle of classic enums and sum types. So it is a bad language design in my opinion.
That was true a decade ago. See modern zero-cost implementation of Option in Rust powered by sum types and pattern matching. |
Note that I don't disagree too much with the conclusions given here, but the use of _ idiomatic Go_ is putting Go up on som artsy pedestal. Most software programming is fairly boring and practical. And often you just need to populate a drop-down box with an enum ... |
|
@bep I think you misread the original comment. "Go idiomatic enums" was supposed to refer to the current construction of using type Foo int + const-decl + iota, I believe, not to say "whatever you are proposing isn't idiomatic". |
@rsc Regarding the If my proposal of a new reserved keyword |
A new keyword is not possible before Go 2. It would be a clear violation of the Go 1 compatibility guarantee. Personally, I am not yet seeing the compelling arguments for enum, or, for that matter, for sum types, even for Go 2. I'm not saying they can't happen. But one of the goals of the Go language is simplicity of the language. It's not enough for a language feature to be useful; all language features are useful--if they weren't useful, nobody would propose them. In order to add a feature to Go the feature has to have enough compelling use cases to make it worth complicating the language. The most compelling use cases are code that can not be written without the feature, at least now without great awkwardness. |
Whats the status on adding enum to golang. There are many issues and this thread also has many conversations, and still there is no progress on this direction and no final conclusion. Can someone add or mark all the complexities involved in making Enum as a fundamental construct in Go, just to understand why its a topic that need this much wait and conversation |
@ysaakpr I'd say the single biggest hurdle is lack of consensus that it should happen and if so, what specific features it should have. I'm pretty sure that if we had consensus on those two, it would be fairly easy to come up with a design accommodating those features. But the assumption of your question is that it will happen and there is just some hurdle preventing it. I would really recommend against viewing any language feature through this lens. Not all languages will have all features and that is a good thing. We certainly might get typed enums at some point, if someone on the Go team decides that we really need them (just like it happened with generics). Until then, I'd personally recommend just assuming that it won't (just like people did with generics). |
Out of these 2 and 3 can be achieved using the current type definition, but the point 1 is not handled in the current type system, and according to me these three are if not there |
@ianlancetaylor Sorry, I forgot to mention that, when the values are fixed, ability to list/iterate over all the possible values of an enum is a must and which is intrinsic with the kind of this type. I understand there are multiple features required, but i am sure this lists is still limited. At least do we have a ticket on which we tracking all the requested behaviours on enum and we can use it for tracking, prioritising and categorising the features |
@ysaakpr That's this issue. Also, to repeat: There's also the question of if we want typed enums at all. There are people (like me) who consider the idea counterproductive or their value at least overstated. It also has semantic overlap with #19412 and it's not clear if we'd need both. And some features are contradictory and need to be weighed against each other. |
I don't know if this is going to help with the discussion, but for my own use, I usually use a tool to go-generate the following functions and methods for my enums:
Out of the three items above, number one is the most useful. I do not think a new type is needed. In fact, I think Go2 is a good opportunity to review existing features and select which one to clean and merge/redesign, and prepare a clean slate for the next iteration of Go. I don't think we should be aggressively adding features to Go2. |
This comment was marked as spam.
This comment was marked as spam.
Enums are supposed to make it simpler. I don't find this type Country string
const (
CountryGreatBritain Country = iota + 1 // Bloated namespace, iota', repeating "Country" over and over, colliding with other keywords that start with "Country" or even with the type itself and thus bloating more and more the package
CountryGreece
...
)
func NewPerson(country Country) { ... }
... { NewPerson(Country(15) } // should NOT be compiled, but compiles, no type-safety which does not have any type-safety guarantees, any compilation features, good autocomplete features etc. type Country enum {
GreatBritain = 1
Greece
....
}
func NewPerson(country Country) { ... }
... { NewPerson(Country.Different) } // Doesn't compile, type-safe, not bloating auto-complete/package namespace, good for safe library usage Im not sure, I could be entirely wrong, I'm not sure I understand the "new keyword breaks compatibility" narrative? Enums have been supported by every language every since C. My understanding is that they have been a great type-safety tool, non-package namespace bloating technique for a long time. Personally I don't find any good arguments for not supporting enums. It's a keyword that has made people's life easier, that helps humans not mess up, makes a language simpler by not having a) Bloated namespace b) Non-type safety for Go's pseudo-enum techniques c) Compiler making sure you don't mess up d) People use your libraries safely and many other reasons. My understanding is that Go's goal is to be a simple language. And Enums, for the end user, seem to achieve just that goal on every aspect. Even in more cases, an
Like in this case type Flag int
const (
FlagRead Flag = iota + 1
FlagWrite
)
func (f Flag) String() { // Messy hard maintenance. What if you add an enumeration and forget to add the String() equivalent? Bugs arise. Isn't Go supposed to make a language simple enough for people to not mess up simple things?
switch f {
case FlagRead: return "Read"
case FlagWrite: return "Write"
default: return "invalid"
}
}
func main() {
f := FlagRead
fmt.Printf("%s", f)
} instead of just having a syntax like type Flag enum {
Read
Write
}
// No need to maintain all the time the String() function
// No need to navigate anywhere to see if there is a String() function or if it will print a number
// No need to navigate between String() and enum
func main() {
f := Flag.Read
fmt.Printf("%s", f) // Compiler compiling it to be printing something along the lines of "Flag.Read"
} another compelling case type RenewableEnergy enum {
Solar = "Solar power"
// No need to navigate between String()
// you're sure that never "invalid" or a runtime error will appear because you forgot to maintain the String() function
Wind = "Wind turbines"
Hydropower // Will just print Hydropower
}
... In general, Enums seem to be helping in many cases to
|
Nobody would accidentally make up a meaningless number except for a deliberate intention to hurt oneself. I think this is a theoretical problem that doesn't happen in practice. |
Actually, the IDE I use provides good autocomplete suggestions based on the type of the constants.
A new keyword breaks compatibility, as existing code using an identifier with the same name as the keyword would no longer compile. |
"Nobody would accidentally do ..." The intention of a good compiler is supposed to prevent even the case of such cases in the first place. Like, cases such as #51317 are heavily debated just because of making sure the compiler and the runtime handles the case in a way that the user does not have to care or will be safe from their own actions. Like, I see all the time arguments from both ways.
always depending on which side of the argument you're on. If Go strives to be safer to use, then you should prevent a user from having such weird cases such as that. And there are cases and have seen or I'm not sure even have written myself I won't put myself in another "im the best programmer" bag, because it was "easy" or comfortable at the time to do so and because the programmer just didn't think too much about it. Where an pseudo-enumeration was written like that in an unsafe manner. |
I may be genuinely wrong, and just curious, how would I don't know, let me know what you think if it would actually break compatibility |
This may be the intention of a proof assistant, but not of a general-purpose programming language compiler. The latter must strike a balance between often contradictory requirements. It should aim to guard against common accidental mistakes, not against deliberate self-sabotage. Otherwise, the compiler wouldn't compile statements like |
I see what you mean, I think you're presenting though two completely separate cases:
edit: Despite that statement, I presented a ton of arguments about enums, let's not narrow it down to just |
This program doesn't compile, even though
Go's parsing of keywords is deliberately context-free. |
@exapsy In regards to backwards compatibility, you need to understand the difference between the scanner and the parser. The scanner takes bytes and converts them into a stream of tokens which each has a kind. A keyword (like The parser then takes the stream of tokens and converts it into an abstract syntax tree, based on the kind of the token. "A function declaration is a If we would introduce a new keyword like To make the parser still be able to parse More problematically, currently a type declaration All of that being said: Yes, today we could probably decide to do it, by guarding it behind a new language version. That is, we would make In particular, we set ourselves the limit that an existing valid program should never be re-interpreted as a different valid program - it should always either break in a new version, or the new version should not be valid today. This is to make it easier to detect these kinds of backwards-incompatible changes automatically - if your program still compiles with the new version, it should do the same thing. A new syntactical construct should be carefully vetted to fulfill that requirement. But yes, we could probably do that today. Note that a lot of this discussion predates having these tools for introducing backwards-incompatible changes in place. On a meta-level: In my experience, when discussions on GitHub issues get new comments with too high a frequency, that is a symptom that the discussion is not being productive. While I typed out this relatively long comment, to give a thorough explanation of all the subtleties, at least two new comments popped up above. Perhaps making parts of my thorough explanation obsolete, but probably not. But it's better to take your time to be thoughtful, in this medium. If new comments happen every few minutes (by the same two or three people) that is a clear indication that the discussion should be taken to a medium more suited for real-time communication (like Slack). |
The Go team discussed the need for enums in the very early days of Go. Enums pose various of problems, some of which have been pointed out here. Extensibility of enums is a real issue. Scope of enum names is another real issue. Orthogonality of concepts is also a concern (enum vs constants vs variables). For these reasons we decided early on not to add a specific enum concept to Go and instead use the There's also no urgent need for enums or an inability to do things in Go that cannot be done relatively easily with existing mechanisms. In short, the bar is high for a significant change such as this, and it doesn't seem that the bar is met. With respect to support for more complex typed "enums", rather than introducing a heavy-weight mechanism, I suggest we investigate #21473 ( |
[like] WeiYaPeng(Jim) reacted to your message:
…________________________________
From: Apostolis A. ***@***.***>
Sent: Sunday, January 14, 2024 7:13:56 AM
To: golang/go ***@***.***>
Cc: Subscribed ***@***.***>
Subject: Re: [golang/go] proposal: spec: add typed enum support (#19814)
simplicity of the language.
Enums are supposed to make it simpler. I don't find this
type Country string
var (
CountryGreatBritain = iota + 1
CountryGreece
...
)
which does not have any type-safety guarantees, any compilation features, good autocomplete features etc.
How is that more compelling and simple than this
enum Country {
GreatBritain = 1
Greece
....
}
Im not sure, I could be entirely wrong, I'm not sure I understand the "new keyword breaks compatibility" narrative?
To me if feels like creating a new lib into the stdlib. As in, it's something new, if somebody doesn't use it, how would it break compatibility?
Even if it breaks compatibility somehow, in a way I still don't understand exactly, why not just place it under Go v2 then
Enums have been supported by every language every since C. My understanding is that they have been a great type-safety tool, non-package namespace bloating technique for a long time.
Personally I don't find any good arguments for not supporting enums. It's a keyword that has made people's life easier, that helps humans not mess up, makes a language simpler by not having a) Bloated namespace b) Non-type safety for Go's pseudo-enum techniques c) Compiler making sure you don't mess up d) People use your libraries safely and many other reasons.
My understanding is that Go's goal is to be a simple language. And Enums, for the end user, seem to achieve just that goal on every aspect.
—
Reply to this email directly, view it on GitHub<#19814 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AAKTSGNL5FII2YOBBCVYSOLYOOATJAVCNFSM4DGA7O32U5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TCOBZGA4DMOBXGUYA>.
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
|
[like] WeiYaPeng(Jim) reacted to your message:
…________________________________
From: Apostolis A. ***@***.***>
Sent: Sunday, January 14, 2024 9:12:25 AM
To: golang/go ***@***.***>
Cc: Subscribed ***@***.***>
Subject: Re: [golang/go] proposal: spec: add typed enum support (#19814)
... { NewPerson(Country(15) } // should NOT be compiled, but compiles, no type-safety
Nobody would accidentally make up a meaningless number except for a deliberate intention to hurt oneself. I think this is a theoretical problem that doesn't happen in practice.
"Nobody would accidentally do ..."
The intention of a good compiler is supposed to prevent even the case of such cases in the first place. Like, cases such as #51317<#51317> are heavily debated just because of making sure the compiler and the runtime handles the case in a way that the user does not have to care or will be safe from their own actions.
Like, I see all the time arguments from both ways.
* Go has to be simple and make sure the user is safe
* We do not have to protect the user from making Country(5)
always depending on which side of the argument you're on.
If Go strives to be safer to use, then you should prevent a user from having such weird cases such as that. And there are cases and have seen or I'm not sure even have written myself I won't put myself in another "im the best programmer" bag, because it was "easy" or comfortable at the time to do so and because the programmer just didn't think too much about it.
Where an pseudo-enumeration was written like that in an unsafe manner.
—
Reply to this email directly, view it on GitHub<#19814 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AAKTSGKGQWF2HSQSUY2UDMTYOOOPTAVCNFSM4DGA7O32U5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TCOBZGA4DSMZVGEZA>.
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
|
[like] WeiYaPeng(Jim) reacted to your message:
…________________________________
From: Apostolis A. ***@***.***>
Sent: Sunday, January 14, 2024 9:21:58 AM
To: golang/go ***@***.***>
Cc: Subscribed ***@***.***>
Subject: Re: [golang/go] proposal: spec: add typed enum support (#19814)
good autocomplete features
Actually, the IDE I use provides good autocomplete suggestions based on the type of the constants.
To me if feels like creating a new lib into the stdlib. As in, it's something new, if somebody doesn't use it, how would it break compatibility?
A new keyword breaks compatibility, as existing code using an identifier with the same name as the keyword would no longer compile.
I may be genuinely wrong, and just curious, how would type MyEnum enum {} break compatibility? Where you would actually use this as a golang programmer in a way that would break compatibility? It's not like you would use this format as a variable format, you cannot. Maybe some other way that can break compatibility?
I don't know, let me know what you think if it would actually break compatibility
—
Reply to this email directly, view it on GitHub<#19814 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AAKTSGP26ICH5CVX6IHQPP3YOOPTLAVCNFSM4DGA7O32U5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TCOBZGA4DSNJVGU4A>.
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
|
[like] WeiYaPeng(Jim) reacted to your message:
…________________________________
From: Apostolis A. ***@***.***>
Sent: Sunday, January 14, 2024 9:27:53 AM
To: golang/go ***@***.***>
Cc: Subscribed ***@***.***>
Subject: Re: [golang/go] proposal: spec: add typed enum support (#19814)
The intention of a good compiler is supposed to prevent even the case of such cases in the first place.
This may be the intention of a proof assistant, but not of a general-purpose programming language compiler. The latter must strike a balance between often contradictory requirements. It should aim to guard against common accidental mistakes, not against deliberate self-sabotage. Otherwise, the compiler wouldn't compile statements like os.RemoveAll("*") or similar.
I see what you mean, I think you're presenting though two completely separate cases:
os.RemoveAll("*") is code that does something, you should not prevent the programmer from executing code as a compiler at least imo.
Country(5) is syntax which can just be improved massively by just having enums
—
Reply to this email directly, view it on GitHub<#19814 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AAKTSGP7HGQGX4JVSP5Z22TYOOQJTAVCNFSM4DGA7O32U5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TCOBZGA4DSNZQG42Q>.
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
|
I'd like to propose that enum be added to Go as a special kind of
type
. The examples below are borrowed from the protobuf example.Enums in Go today
How it might look with language support
The pattern is common enough that I think it warrants special casing, and I believe that it makes code more readable. At the implementation layer, I would imagine that the majority of cases can be checked at compile time, some of which already happen today, while others are near impossible or require significant tradeoffs.
SearchRequest(99)
orSearchRequest("MOBILEAPP")
. Current workarounds include making an unexported type with options, but that often makes the resulting code harder to use / document.Things to Consider
enum
on top of the type system, I don't believe this should require special casing. If someone wantsnil
to be valid, then the enum should be defined as a pointer.I don't have any strong opinions on the syntax. I do believe this could be done well and would make a positive impact on the ecosystem.
The text was updated successfully, but these errors were encountered: