13 ms·
Go Enums Suck
- RamblingCTO 3y agoIf people are spending this much time circumventing your language design, you oughta take a look inside. Go "enums" suck and limit the language.
- the__alchemist 3y agoGo and Python have OK enums. I will use them, but they could be simpler/more expressive. This begs the question: Is there an obstacle to releasing better enums in the next Python and Go versions? If the concern is about breaking backwards compatibility, I would be OK with a new type. Is it a culture issue, ie that Python and Go programmers don't use enums much? (Chick + egg here) Rust's enums are great. No "auto" boilerplate if not mapping to an integer, exhaustive pattern-matching, sub-types etc.
- randomdata 3y ago> Rust's enums are great. Rust doesn't have enums. It has sum types – that for some reason it arbitrarily decided to call enums. Sum types are great. There is a good case to be made that Go would benefit from the addition of sum types. But until that day there isn't much more you can do with enums. That's all enums are – a set of named constants.
- j16sdiz 3y agoSum type are great, but I don't think it fits in go type system.
- bmoxb 3y agoThey also tend to require proper pattern matching to be particularly useful, something which I can't see being added given Go's design philosophy.
- the__alchemist 3y agoI've heard this before, but I have a struggle understanding the abstraction. I make heavy use of rust and Python enums (Are they both misnamed?) Those + structs are generally the base of how I structure code. The "enums" in the article also seem to be of the same intent. Is this a "no true Scotsman" scenario? Some research implies the difference is a True Enum involves integer mapping, while a Sum Type is about a type system. I think both the Rust and Python ones can do both. (repr(u8) and .value for Rust/Python respectively) The use case is generally: You have a selection of choices. You can map them to an integer or w/e if you want for serialization or register addresses etc, but don't have to. Is that a sum type, or an enum? Does it matter? Another thought: Maybe: #[repr(u8)] enum Choice { A = 1 B = 2 } Is an enum, while enum Choice { A(C) B(D) } Is a sum type?
- jerf 3y agoEnumerations back to integers. Enumerations can have iterators written on them that exhaustively enumerate the possible values. (Sum types either have no such enumeration at all, or in general, they're useless, so you don't see them.) Enumerations can be represented by a canonical and small set of strings, if you want a string backing them. This is what an enumeration is, partially because that's precisely what the word "enumeration" means; the ability to assign an ordinal number to each value in the enumeration. To "enumerate" a set is to assign integers to them. In Python, for instance, see the "enumerate" function, which does exactly enumeration on the output of some iterator. Sum types can be used to represent enumerations, but it's very restrictive subset of sum types. Trying to understand what a "sum type" is through the lens of a single integer would be a very strange way to approach them. Nor are sum types a "superset" of an enumeration; a base sum type is not an enumeration. You need to add more things to it to get an enumeration. In a Venn diagram they're the classic two cicles with some overlap in the middle but with distinct bits on each side. I do not understand the strangely active desire some people seem to have to erase the distinction between these two things, as if some advantage will result, as if sum types will somehow become more useful than they are or as if they will somehow lose their abilities if we don't also call them enumerations. There is no advantage to smudging these two unique things together. Not saying that you are promoting this per se, the__alchemist, just that I've seen it a lot and I don't get it. It's like someone wanting to claim that database and files are really the same thing; well, sure, there's some overlap, but each does many things the other doesn't and trying to squint until they actually are the same thing is generally the exact wrong direction to go to attain understanding. To put it another way, when adding an "enumeration" into a network protocol, you allocate some fixed number of bits to hold a given sized integer. When you add "a sum type" into a network protocol, you have a lot more work to do in general. To put it yet another way, enumerations have meaningful implementations of a ".Next()" that a sum type really doesn't. If you have a sensible implementation of a given method on one type of thing and it's not sensible on some other thing, then clearly they can not be the same thing. (I say multiple times that a sum types doesn't have such an implementation in this message. By that I mean that while it is trivial to have a "data Color = Red | Green | Blue | RGB Int Int Int" and implement an iterator to walk through all possible values, it is not something that is generally done for all sum types, and if the sum type also includes functions or other complex values it isn't in general possible at all in common programming languages. Again, writing an interator for "all possible functions" is perfectly theoretically possible, but in engineering terms not something anyone would actually do. All enumerations can be iterated.)
- waych 3y agoTrue Scotsman spotted!
- deleted 3y ago[deleted]
- samatman 3y agoRust's enums are entirely unlike C enums, and reasonably similar to Java enums. Not the first time a word has been used for several nearly-unrelated concepts, and it won't be the last.
- pwdisswordfishc 3y agoThat's because what you insist is the only thing deserving of the name "enum" is just a sum type of unit types.
- deleted 3y ago[deleted]
- BugsJustFindMe 3y ago> Go doesn’t technially have Enums and it is a missing feature in my book but there is a Go idiomatic way to achieve roughly the same thing. Oh, so it's a lot like Python then. > This is fine however it is nothing but an integer under the covers this means what we actually have is: Oh, so it's a lot like C++ then. > But what you notice here is we have no string representation of these Enums so we have to build that out next Have to! Yes this still all sucks. (But at least there's ugly historical precedent!)
- ollien 3y agoPython does have enums in its standard library https://docs.python.org/3/library/enum.html https://docs.python.org/3/library/enum.html
- deleted 3y ago[deleted]
- BugsJustFindMe 3y agoHeh. Python's enums are not real types, they're just funny classes, so you still have to do dumb things like assign an internal value (commonly a dumb int) and were (and probably still are) wildly deficient in many other commonly-desired ways for a very long time. A bunch of things covered in this blog post (like StrEnum and EnumCheck) weren't added until pretty recently.
- zelos 3y agoC++ added enum classes a while back, though.
- pkulak 3y agoGo is in this weird middle ground where it's modeled after C, so it's got things like no enums, return codes for errors, mutable everything, nulls, and pointers (that don't support arithmetic, so it's really just this "*" sigil that you have to remember to use sometimes), but it's also fully garbage collected and has built-in, stackful green threads. I have no idea what it's actually trying to be.
- sgift 3y agoI love Go. Especially how the devs stubbornly refuse to learn anything from Java, but stumble boneheaded into everything that Java solved over the years. Generics? We don't need that .. (time goes on) .. okay, damn it. Here! Generics! Enums? We don't need that, just do iota/integers! How long will it be this time until the Go devs accept that Java Enums are a safer and better abstraction over integers for the cases where you'd want an Enum? And that they allow something like EnumSet, which are type-safe bitsets, without everyone having to do that by hand?
- neonsunset 3y agoJava generics aren't even proper generics. For that better look at Rust and C#.
- Zambyte 3y agoIn what sense? Because they only apply to non-primitive types?
- HideousKojima 3y agoNo, Java generics are basically syntactic sugar over casts, which is why types are erased at runtime when you're trying to debug. Performance also isn't as good as for C# generics since the Java approach limits optimizations.
- downWidOutaFite 3y agoThat's a minor implementation detail that devs almost never have to think about.
- jimbob45 3y agoYou'll run into it with generic arrays[0] in Java which are reasonably common. [0]https://www.baeldung.com/java-generic-array https://www.baeldung.com/java-generic-array
- SamWhited 3y agoI generally agree that this is a big problem with Go, so I don't want to quibble too much, but the author acknowledges that the language doesn't have enums and that they're just trying to use this feature like enums (TBF, this is common advise on the internet and a lot of code does this): instead the author should be thinking "how do I solve this problem without enums since they don't exist?" I'd be willing to bet that there's just a better way to do whatever the actual real-world example they want to achieve is (this was not entirely clear to me from the examples in the post). Like I said though, that doesn't mean that (real) enums wouldn't be an even better way to do it than whatever the Go way is for a given problem, so I don't want to quibble too much since I think this is one of my biggest day-to-day complaints about Go, but it's worth pointing out that the premise can be flawed and that it's still a problem in the language, these two things aren't completely orthogonal. TL;DR — Instead of pulling in a code generator and another library, it may be good to think of alternate ways to do the same thing without a lot of extra code footprint.
- coldtea 3y ago>instead the author should be thinking "how do I solve this problem without enums since they don't exist?" Which is exactly what they do in the post. They still have every reason to complain about Go's oft suggested lame substitute.
- geodel 3y ago> They still have every reason to complain about Go's oft suggested lame substitute. Well, yeah, this is also the reason they deserve quite a bit of ridicule from actual Go users.
- j16sdiz 3y agoI can't really think how this could be done. Other languages either substitute enum with primitive type, string, or use strong type system tricks. Go do duck typing, .. so..
- avgcorrection 3y agoThe author considered the Go pattern for “enums”. Found it lacking. Made their own code generator for their preferred pattern. That’s two options. What are the others that are meaningfully different? You have to be able to deal with simple “sum types” in the sense of: this type could take on the value of one of these X predetermined constants. This requirement doesn’t disappear just because the language doesn’t directly support it.
- mseepgood 3y agoGo doesn't have enums and as such they cannot suck. Something that is non-existent can't be good or bad. The title is clickbait. Go has constants, and they have a great feature for defining constants (iota).
- randomdata 3y ago> Go has constants That's literally what enums are: A set of named constants. You might be thinking of what is traditionally known as sum types, which some people have recently started calling enums[1]. Indeed, Go does not have sum types. [1] Presumably because of Rust using the wrong term when specifying its sum types
- khazhoux 3y agoThe article is about enums, which are lacking in Go.
- randomdata 3y agoNo, Go definitely has enums. It is sum types (that some people have recently started calling enums) that Go lacks.
- grumpyprole 3y agoGo does not model enums as separate types (like e.g. Pascal), they are essentially just integers like C.
- randomdata 3y agoNo, Go enums are definitely separate types. Sure, technically there is also an integer (or some other base representation) hidden in there somewhere, but that's what an enumeration is. Without that you don't have an enum.
- 3y ago
- coldtea 3y agoGo's iota is probably one of the worst ideas in all programming languages. Not a full typesafe enum type, the same clunky "enums" (assigned constants) available in C, but they bother to implement an auto-incremented counter. So you can't depend on the enum for exhaustiveness warnings e.g. on switch statements, type checking, or correctness, but you do get a useless numeric association autogenerated with iota - so that you can lose the association if you re-order your enum values that you have serialized earlier and want to reload in the future.
- Steltek 3y agoI love iota! It comes in handy everywhere. Don't serialize to raw integers unless you absolutely have to. Serialize to a string value: it's future/oopsie proof and helps with debugging. The nature of iota is pushing people away from bad habits. But yes, getting warnings about missing enums in switch statements is very handy. But Golang's type system never aspired to be as rigid and encompassing as C++, Haskell, Rust, etc.
- coldtea 3y ago>But Golang's type system never aspired to be as rigid and encompassing as C++, Haskell, Rust, etc. Well, didn't have to aspire to all that to at least make an effort to be more helpful, especially in trivial aspects, like having an actual enumerated type, or an Optional/Error type...
- preommr 3y agoI don't think you understand how minimalist Golang is... the std lib only has room for anything you would ever need for a web service including a full web server, builtins like hashmaps, a bunch of things for concurrent programming like channels, go routines, etc. There's also language features like returning tuples and destructuring them which is actually more advanced than it's peers. To include an optional type would be against go's identity.
- deleted 3y ago
- taeric 3y agoA thing that so many enum solutions miss is that you have to have a path for a value outside of the current definition, or you lose a lot of flexibility in compatibility with any data that crosses wires or disks. Sounds fine for a lot of cases, of course, but in a world of mixed deployment fleets working on data, you pretty much have to have a way to allow a value that is not part of your current definition, or you are basically placing a "poison pill" on your system.
- scrubs 3y agoI'm not bent out of shape about go enum's like the OP. However when it comes to writing and reading data over networks or disk io a naked enum was never going to work anyway, not really. Then you turn to protobuf etc so one has a cross os/arch/cpu interoperability
- taeric 3y agoI don't disagree, I don't think. Just wanting to float a reason simpler enums are usually preferred. In particular, you likely want to use the enum to restrict what values you will introduce into a system. You often, sadly, cannot use them to restrict what values are actually there. Which is why the place you pass them will see the raw int.
- eptcyka 3y agoYeah, but then should structs ever have a bounded size? Someone might well have gone and done did added a couple of fields to a struct over the wire.
- taeric 3y agoI think I touched on what I feel is the right answer here. The data is separate from the enum. That part is clear and I don't think anyone really disagrees. What, then, is the enum for? It is specifically to restrict what your code will do. To that end, it is a good way to restrict data your code can introduce. It is not a restriction on what is happening outside of your code, though. You can, of course, make similar arguments for structs. Or really any data in the code. How do you know your number won't go over some arbitrary size? Enums are, largely, the most restrictive data in code. Which is why we discuss them more, I think. If folks did work with more big numbers, I'm sure we would be more curious on why things don't act like common lisp where big numbers basically work with no extra work.
- kevmo314 3y ago> Anywhere that accepts an Operation Enum type will just as happily accept an int. This is a real pain as it almost completely negates the work we have done here. Is this a real problem? If there's a function signature that accepts `Operation`, the caller must explicitly cast the `int` to `Operation`. At that point, it's the caller's own fault. So I'm not really following what this is solving. As demonstrated in the article, sometimes you want string constants, sometimes you want `iota`, other times you want `1 << iota`. I like that Go doesn't dictate which I have to use if I declare an "enum".
- glenjamin 3y ago> Is this a real problem? If there's a function signature that accepts `Operation`, the caller must explicitly cast the `int` to `Operation`. At that point, it's the caller's own fault. You would think that, but that isn't always the case: https://play.golang.com/p/Ze3pfNEVTVs https://play.golang.com/p/Ze3pfNEVTVs It's very easy to create an enum value that isn't actually in the defined range
- mseepgood 3y agoCan you explain the thought process of a developer when they write 'performOperation(2)'? What do they believe '2' signifies in this context? I struggle to believe that this could occur by accident.
- avgcorrection 3y agoYou struggle to imagine a programmer passing a value of the wrong type to a function?
- bb88 3y agoOr passing a wrong value and the compiler allows it because the programmer trusted the compiler to "always do the right thing".
- mathiasgredal 3y agoIf you are already using gRPC in your codebase, then you can define your enums with Protobuf, which does much of the same as the tool shown in this article.
- neonsunset 3y agoAh yes, gRPC, something using which requires more ceremony and worse UX in Go than in …C# of all the languages.
- reactordev 3y ago“Anywhere that accepts an Operation Enum type will just as happily accept an int.” Hey! Just like in C! I digress, I think the issue is that the author comes from another language where enums are a thing. In go, they aren’t. Enums should be types. Types that don’t infer to an int. Use an interface. Be happy.
- Scubabear68 3y agoIt has always been surprising to me how primitive Go really is, for no really good reason. I understand the evolution of C, it made perfect sense back when it was invented. And the limitations were necessary due to the wide array of architectures and extremely limited computers of the time in every dimension (CPU speed, IO speed, RAM size, disk size, etc). Many of those dimensions have been improved by several orders of magnitude, and both compilers and runtimes can afford to be comprehensive. Yet we get this ham-strung language out of the gate. Very disappointing.
- pjmlp 3y agoIt is basically Limbo with updated syntax, and AOT instead of a JIT.
- andreimackenzie 3y agoSimplicity is a feature. Sure, how complicated can effective enums really be, but Go's general philosophy is to think hard (& sometimes for a long time) before adding every bell and whistle. I have a far easier time delving in to previously unknown Go code for the first time compared to something like Scala (or even Java). Go is a solid language for those who value that and want to enable the experience for others.
- Scubabear68 3y agoSimplicity can be taken too far. Take this to its logical close cousin and you would have Forth or Basic. Also, we have been doing computer language design for quite awhile now. This isn’t a new frontier. The deficiencies in Go aren’t in areas of “oh, we never thought of that!”, but are in very well known areas with known solutions. I find Go code is obscured with house keeping code that isn’t necessary in better languages.
- OhSoHumble 3y agoThat simplicity can lead to more complex and hard to read/support code. For example, to encode a JSON structure with a dynamic top-level key you need to write a custom marshaller OR marshal twice. That's... awful. Like bonkers level insane.
- pjmlp 3y agoPascal in its original 1970's design, type myEnum = (value1, value2, value3, value4) Naturally that is too advanced and slows compile times.
- leosanchez 3y ago> Naturally that is too advanced and slows compile times. Sarcasm ?
- pjmlp 3y agoOn the spot.
- mseepgood 3y agoWirth regretted it later and didn't add it to Oberon. Quote from "From Modula to Oberon": "Enumeration types appear to be a simple enough feature to be uncontroversial. However, they defy extensibility over module boundaries. Either a facility to extend given enumeration types has to be introduced, or they have to be dropped. A reason in favour of the latter, radical solution was the observation that in a growing number of programs the indiscriminate use of enumerations (and subranges) had led to a type explosion that contributed not to program clarity but rather to verbosity. In connection with import and export, enumerations give rise to the exceptional rule that the import of a type identifier also causes the (automatic) import of all associated constant identifiers. This exceptional rule defies conceptual simplicity and causes unpleasant problems for the implementor."
- pjmlp 3y agoI know, and there is a reason why my favourite descendant from Oberon is Active Oberon, and not what Wirth pursued after 1992. Oberon-07 minimalism doesn't make Go better.
- mseepgood 3y agoYou can disagree with design philosophies, but Wirth and the Go designers probably have thought more about these things than you.
- jurschreuder 3y agoYou should not use these enums with ints++ in security sensitive applications, because they're sensitive to rowhammer attacks. Use uint64s with minimal bit overlap. Maybe nice to include in this ultra-advanced enum libray
- Rapzid 3y agoEnums suck in a bunch of languages including C#. It has binary compatibility issues that need consideration along with some other gotchas and shortcomings. So much so that much of the dotnet official stuff, ie asp.net, use static classes with string fields instead of enums. Unfortunately that doesn't play well with libraries that have enum support like entity framework. PITA. One saving grace is the ability to create extension methods on enums.
- parhamn 3y agoAnyone writing a compiles to go, go++ yet? There are generators for better enums, sum types, and more. Bring them all together! I'm only kinda joking. Im also curious now if the Typescript checker was written in a way that it could be adapted to new languages easily.
- thegeekpirate 3y agohttps://github.com/goplus/gop https://github.com/goplus/gop, but they go slightly too overboard imo.
- coffeebeqn 3y agoThat looks nothing like go. I wonder why even call it go+ at that point
- baranul 3y agoIt reminds me of Vlang (vlang.io), but where Goplus is a half way step between Go and them. It strikes one as, a lot of people felt there are things missing in Go that they wanted, but how Go is governed it would never be possible to add or make such changes to the language. So we have Vlang and Goplus. In the case of Goplus, it compiles to Go. Speaking of which, Vlang allows transpiling from Go and possibly to Go, but from and to C is more of their priority. The intent of the Goplus author and contributors seems to be that their people could easily switch between both, but that their version is more feature rich.
- pjmlp 3y agoThey already exist, D, C#, Odin, Nim... no need to insist when the community is so down into minimalism.
- bmoxb 3y agoThat actually sounds like an ok idea imo - some more quality of life features but with the small executables and fancy runtime of Go would be pretty nice.
- kleton 3y agoI use this one https://github.com/abice/go-enum https://github.com/abice/go-enum
- lantastic 3y agoI like the "go generate" approach to integrate such tools. I used a different (but identically named) go-enum tool [0], which accepts the go type and generates implementations for a bunch of interfaces. The neat thing is that the starting point is the "idiomatic" go enum definition, rather than description via a separate DSL: //go:generate go-enum -type=State type State int const ( Unknown State = 0 Disconnected = 1 Connected = 2 ) which then generates a separate file with implementations such as: func (i State) MarshalJSON() ([]byte, error) { ... [0] https://pkg.go.dev/github.com/searKing/golang/tools/go-enum#section-readme https://pkg.go.dev/github.com/searKing/golang/tools/go-enum#...
- llmblockchain 3y agoIt's kind of strange to see them complain about enums and then promote a DSL-specific tool they made for generating enums. At the same time, Go has generators built in and can generate enum tables, enum to strings, and other things they have shown. I am unsure why they didn't do it the "Go" way.
- mkesper 3y agoThey complain that Go has no usable, type safe enum and then show a way to build them in Go.
- zarldev 3y agoDSL? The go way is to use go generate I would say and it can be used with go:generate, I would like to use the AST lib to parse go files to remove the need for any json and to be more like the cmd stringer tool.
- llmblockchain 3y agoYes. Your DSL is JSON in this case, { "enums": [ { "package": "cmd", "type": "operation", "values": [ "Escalated", "Archived", "Deleted", "Completed" ] } ] } My point was the "Go" way to do this isn't parsing a custom format (like your JSON), but it's to use go generate.
- zarldev 3y agoBut all go generate does is run a binary like stringer? This can be used in go generate.
- mariusor 3y agoI'm not sure I understand exactly what you mean, but you can combine go:generate with go run so you can execute code from the current module/project that does what you want. //go:generate go run ./internal/enumhelper -flag1 -flag2
- zer00eyz 3y ago>>> But what you notice here is we have no string representation of these Enums so we have to build that out next, for this I just use a map[Operation]string and instantiate this with the defined string constants. Cries in I18N...
- mseepgood 3y agoOr just define the constants as string constants in the first place: "type Operation string"
- zoogeny 3y agoI have a totally tangential ramble queued up on this topic. I like philosophy and I read it as a total amateur. Naming is a big topic in modern philosophy [1] with a huge amount of depth. I think of it in terms of my naïve understanding of Wittgenstein's later work and the idea that the meaning of a word actually comes from its usage within the context of a set of collaborating agents. If I say to a programmer "use a vector", that will mean something specific if we are writing C++ and I want to use a resizable array. And it could mean something totally different in the context of a 3d rendering engine. I think of how often I see words like "Context", "Session", "Kernel" and all of their myriad uses. So I see articles like this as just a pointless argument because we are crossing some boundary between distinct language games. The author of this article thinks "Enum" means one thing. But it is actually the case that "Enum" is unspecified outside of some particular context. And in this case, the author is bringing some outside context and trying to reuse it inappropriately. 1. https://en.wikipedia.org/wiki/Naming_and_Necessity https://en.wikipedia.org/wiki/Naming_and_Necessity
- mcphage 3y agoThis is the "you're holding it wrong" of linguistic arguments.
- zoogeny 3y agoTo be clear, I'm not saying the author is wrong. The author wants a thing that Go doesn't have (a thing that I would also like to have in Go). He is calling the thing he wants an "Enum". Then he is insinuating that Go actually has the thing he wants but it sucks. It is confusing because Go does have a thing that some people also call "Enum". What I'm saying is the thing he wants and the thing Go has may share a name but they aren't the same thing. Just like C++ has "vectors" and OpenGL has "vectors" that share some superficial similarities but are ultimately totally different things. If someone wrote an article that said "OpenGL vectors suck" and then mentioned it missed a bunch of features available in std:vector as justification, most people would recognize this error and dismiss the discussion.
- danenania 3y agoI'm working on an LLM project in Go and the term "context" is overloaded to the hilt--I use it to refer to the LLM context as well as Go's `context.Context` which I'm using all over the place. It's made worse since the most natural plural of context is... context. My solution is to use `ctx` for Go context, `contexts` for arrays of LLM context structs, and `contextPart` for individual context structs. "model" is another one like that since I'm using it to refer to both data models and ML models. And "prompt" since it can be an LLM prompt or a terminal prompt for the user (this is a CLI tool). Differentiating all these overlapping terms in ways that aren't super confusing is definitely a challenge.
- marklar423 3y agoIf you're using protobufs anyway, you can get also get this functionality generated by using a proto enum: https://protobuf.dev/reference/go/go-generated/#enum https://protobuf.dev/reference/go/go-generated/#enum. It works really nicely and has all the features mentioned in the article. One added benefit is it serializes/deserializes safely (even when you add / remove values), so you can persist and read back values without a problem - even to a different language.
- shayarma 3y agoenums are handled poorly in so many language. very confusing.
- skybrian 3y agoThe root cause is wanting to support fixed-size array types in structs, which means nothing can require initialization and everything needs a zero value. The caller can just modify every field directly. This is sort of like how network protocols can’t statically guarantee enums are valid either. When sending bits over the wire, you can send any bits you like. There can be “values reserved for future use,” but to deny their use, you need a runtime check. A similar solution works in Go. A runtime check in a constructor function will fix it. The enum’s value would need to be returned as an unexported field in a struct, which is the only way to guarantee that it’s not writable, except by copying it from another valid value. I don’t see a particular reason why Go couldn’t make this easier.
- divan 3y agoIt's actually the coolest thing about Go enums, that they just it – enums. Developers with a background in other languages assume all enums' use cases need string representation. Well, no. They are needed sometimes, but not always. The same with the ability to pass int to the enum. Author says: > Anywhere that accepts an Operation Enum type will just as happily accept an int. Well, this is simply not true. [1] You'll have to cast your int into your enum, which is totally fine if it's your intent. Granted, there are plenty of valid cases where you need validated input, especially for the public libraries. But hey, not every code is a publicly facing library, and not all need this validation. Why spend CPU and memory on something that probably won't be needed, and that can be implemented with the existing language primitives? In the end, the author did a great job of solving his own requirements around enums and even wrote a code generator that helps him generate this for millions of enum types per second. :) [1] https://play.golang.com/p/Ch-IZ26p0v8 https://play.golang.com/p/Ch-IZ26p0v8
- brabarossa 3y ago>> Anywhere that accepts an Operation Enum type will just as happily accept an int. >Well, this is simply not true. As comment above [1] pointed out `printOperation(2)` is still valid. [1] https://news.ycombinator.com/item?id=39565640 https://news.ycombinator.com/item?id=39565640
- divan 3y agoNot trying to nitpick here, but '2' is not an int here. It's the constant evaluated at compile time. But yeah, valid case if users of your code are in the habit of typing magic numbers into your function that expects enum. My understanding is that on a practical level, these kinds of issues arise from misusing types (or not caring about them) and naively putting variables of one type into the function that expects another. Examples with number constants typed in manually do not hold ground.
- mojuba 3y agofunc (o Operation) IsValid() bool { if o == Unknown { return false } return true } Why, oh why don't people just write return o != Unknown This is so common in the code that I'm seeing on the Internet, on GitHub etc. Is it because people don't understand booleans?
- konart 3y agoThey don't use linters either.
- code_runner 3y agoI think it depends on your definition of "clean code". I do personally like the more succinct version, but "never use a negative condition" makes people do.... stuff like this
- odyssey7 3y agoA good linter can help people see where their code isn't concise and give nudges toward better style. Clearly, being concise can sometimes mean being opaque, but in this case it's not, the boilerplate dilutes the meaning without adding value. Go was developed as a highly opinionated language in which this style of imperative code is apparently preferred. Similarly, the ternary operator shines as a way to use a single assignment to obtain a value that follows from a concise boolean expression, but the ternary operator is wholly omitted from Golang. Although, being opinionated is not in itself a bad thing. If you want to create something, it helps to have a viewpoint that gives you something to say.
- nzach 3y agoThis is in line with 'guard clauses' and this is why it is accepted as idiomatic code. For this example we only have a single condition but as soon as you add more conditions it start to get out of hand. I prefer the original code because it makes the codebase as a whole easier to read. But I don't think there are any 'hard facts' to support using either of these styles over the other in these simple cases.
- mojuba 3y ago
- t0astbread 3y agoWhat I like about Go is not the language itself (I'm not a language designer but I dislike a lot of choices that Go makes) but the entire culture around it of doing things the idiomatic way and moving on. I'm someone who, if you give them a tool that's flexible, will spend time optimizing it. And I'm already busy optimizing other stuff so it's nice to have something constant to build upon. Oh and you don't have to use large power-hungry IDEs that don't integrate with any sort of config management to get a decent experience! (/hj) If I ever learn Haskell it's over for y'all though. (Agree with OP btw, using codegen to get the enums I want is a workable remedy for Go's lack of enums.)
- deleted 3y ago[deleted]
- 015a 3y ago`iota` is maybe the only language feature of Go that I would actually support removing. Obviously, they never will because it would be a breaking change. Its just so vestigial. There's literally no reason to use it, and a very big reason why you shouldn't: The encoded value can change any time you re-compile your program, so you can't actually use it for anything where the value of the enum leaves the process that instantiated it (e.g. marshaling to JSON and sending over the wire). That's a terribly poor characteristic for a feature in a "systems" (emphasis on plural) programming language to have, one would think.
- neilalexander 3y ago> The encoded value can change any time you re-compile your program, so you can't actually use it for anything where the value of the enum leaves the process that instantiated it This is not true, iota is stable in its ordering. https://go.dev/ref/spec#Iota https://go.dev/ref/spec#Iota
- mattrick 3y agoI think they may be referring to if someone accidentally changes the ordering, either by inserting a new variant between two existing ones or by shuffling the order of the existing variants the value can change and cause problems.
- beautron 3y agoYes, but even with that interpretation, they claimed something much stronger: > The encoded value can change any time you re-compile your program Any value (not just enums) can change any time you re-compile your program, if some programmer goes in and messes it up. The real, much softer criticism would be that Go requires its programmer's to understand the potential consequences of inserting or shuffling enum values (where iota is involved). It's a much weaker case against iota than what they stated.
- 015a 3y ago
- ilove_banh_mi 3y agoAda has excellent enumeration types, and subtypes, and compile-time checking of case statement coverage for enum values, and an optional representation mechanism to control the binary value for each (symbolic) enum value. I'm not aware of any other language with that kind of enum type system. See e.g. https://adaic.org/resources/add_content/docs/craft/html/ch05.htm#5.9 https://adaic.org/resources/add_content/docs/craft/html/ch05...
- sydbarrett74 3y agoAda rocks. It's just about the most underappreciated language ever.
- pyjarrett 3y agoNot only that, but when you define an enum you get 'Pred and 'Succ to move between values, range iteration over all values with 'Range and 'First and 'Last, string conversion with 'Image and parsing with 'Value.
- ithkuil 3y agoRust I think covers most if not all of Ada features you mentioned
- ilove_banh_mi 3y agoRust's "enum" mechanism is really an algebraic data type, and corresponds to Ada's enumeration types when applied as discriminants in variant record types. But Ada's enumeration types have a wider context of use, separate and independent from variant record types, including compile- and run-time features related directly to a) symbolic order and symbol names, b) binary representation mapped to these symbols, and c) subtyping/ranges. I'm not aware of a good online presentation focused exclusively on Ada's enumeration types and their various uses. It's not even singled out in the Rationale documents for the design of the language and the (3?) design revisions since the 1980 launch; maybe the AARM (Annotated Ada Reference Manual) has more focused discussions? I'm not sure, I haven't looked at these since ~20y ago.
- pipeline_peak 3y agoThis feature that technically doesn’t exist sucks
- beautron 3y agoI love Go's lightweight, somewhat implicit style of doing enums. You just declare an int type, and then a list of constants of that type. People are complaining about 'iota' here, but I think it's slick and great. It combines so nicely with eliding types and values from subsequent const declarations: type MyEnum int const ( Value1 MyEnum = iota Value2 Value3 ... ) Nice and simple. Most of the syntax is just the enum value identifiers. And it works well for bit flags too: type MyFlags int const ( Flag1 MyFlags = 1 << iota Flag2 Flag3 ... ) Most of the above syntax isn't specific to enums (so you're already getting a lot of other things from it). The only enum-specific syntax is iota and the eliding type/value rule. People seem to want their languages to have all sorts of guardrails, but I find many of these cumbersome. Go gives me the one enum guardrail I care about: The enums are different types, so I can't use a MyEnum as a MyFlag, or vice versa. I've worked on giant Go codebases, with Go-style enums all over the place, and the lack of compiler-enforced exhaustive enum switches just hasn't been a problem. And it's nice to be able to use non-exhaustive switches, when you want them. Go is simple and flexible here. The article criticizes Go incorrectly with statements such as these: > This also means any function that uses these or a struct that contains these can also just take an int value. > Anywhere that accepts an Operation Enum type will just as happily accept an int. This is just not true. Here's an example you can run: https://go.dev/play/p/8VGufuxgK6b https://go.dev/play/p/8VGufuxgK6b The above example tries to assign an int variable to a MyEnum variable, and gives the following error: "cannot use myInt (variable of type int) as MyEnum value in variable declaration" This error directly contradicts what is claimed in the article. Perhaps they mean that MyEnum will accept an integer literal, in which case I would argue that a guardrail here is silly, because again the problem just doesn't really come up in practice. Regardless, the author is not being very precise or clear in their thinking.