[HN Gopher] Go Enums Suck
___________________________________________________________________
Go Enums Suck
Author : pionar
Score : 102 points
Date : 2024-03-01 18:09 UTC (4 hours ago)
(HTM) web link (www.zarl.dev)
(TXT) w3m dump (www.zarl.dev)
| RamblingCTO wrote:
| If 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 wrote:
| Go 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 wrote:
| _> 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 wrote:
| Sum type are great, but I don't think it fits in go type
| system.
| bmoxb wrote:
| They 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 wrote:
| I'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 wrote:
| Enumerations 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.)
| the__alchemist wrote:
| This still seems to point towards Rust's enums being
| both, no?
|
| Example: For a network protocol, see the first code
| sample I posted.
|
| For a `.Next()`, add the method. (I did this recently)
|
| Regarding sum types into a network not working, this
| again sounds like the wrapped enums. One way to do this
| is use an integer for the enum variant at index 0, and
| conditionally assign bytes of an appropriate size based
| on the type wrapped for the next set of bytes.
| jerf wrote:
| OK, so now you _are_ advocating for erasing the
| distinctions.
|
| Why? Why is it so important that they be seen as the same
| thing to you? What benefit is gained from it? What
| benefit is gained from blending together a data structure
| that is fixed bit size from a family of data structures
| of variable size, a fairly fundamental difference? What
| benefit is gained from failing to consider the
| fundamentally different uses they are put to? What
| benefit is gained from looking at someone list a set of
| differences between the two, and basically saying, "yeah,
| they're different, but what if not?"
|
| I can name further properties that differ between them.
| All sum types can embed arbitrary other existing sum
| types within themselves, without practical limit.
| Enumerations can not, because A: they may collide on
| which numbers they use and B: even if you remap them, you
| can run out of integers, especially with smaller values
| like byte-sized enumerations. Enumerations may have
| further structure within themselves, such that particular
| bits have particular meanings or values a certain number
| apart may have relationships to each other, or other
| arithmetic operations can be given some meaning; sum
| types themselves do not generally have any such
| relationships. (At least, _I 've_ never seen a sum type
| in two clauses of the sum type are somehow related;
| that'd be bad design of a sum type anyhow. Even if you
| did this to an internal integer contained in a sum type,
| it would be that _integer_ composed in to the sum type
| that had that relationship, not the sum type.) Sum types
| have a rich concept of pattern matching that can be
| applied, enumerations generally do not (some languages
| can do some pattern matching with bits but there 's still
| no deep structure matching concept in them).
|
| I mean, how many differences are necessary before they
| are not the same thing? They can not fit into the same
| amount of memory; one is fixed in size, the other highly
| variable. One is simple to serialize into memory, the
| other has lots of complicated machinery. Each has
| operations generally valid on one but not the other
| (enumeration, pattern matching, sum type's composition
| whereas enums can not generally). The range of valid
| values (or domain, whichever you prefer) is not the same.
| There are languages that have enumerations without sum
| types, in that enumerations appeared in mainstream
| languages _decades_ before sum types were a mainstream
| conversation. In what other ways could they be different?
|
| It strikes me like arguing that ints and strings are the
| same, because honestly, what's the difference between 11
| and "11" anyhow? Even if you're working in a language
| that strives to make the distinction as small as
| possible, you're still going to get in trouble if you
| believe they really are completely the same thing. And
| any programmer who goes through like truly thinking 11
| and "11" are the same thing is in for a lot of confusion
| as concepts they should be understanding as separate,
| even if at times superficially related, are actually the
| same.
| the__alchemist wrote:
| I'm not advocating for anything; I love Rust Enums and
| use whatever is close to them in other languages, which
| is usually better than the alternative of matching
| strings or similar (A convention in Python).
|
| When I hear "These aren't really enums", my first
| reaction is to dive in and do research. (I'd been down
| this road before, probably after a similar HN
| comment...), but I haven't found usable or practical
| conclusions. It seems like the distinction is too subtle
| to be of use.
|
| Stated more succinctly, let's call Rust enums "Choices",
| as I think this is causing semantic trouble. "Choices"
| are an excellent tool.
|
| I'm looking at this from an engineering perspective; not
| a CS or abstract mathematics one.
|
| I am curious what your pure Enum, and pure SumDataType
| look like in practice. I am also curious what existing
| implementations of either exist. Are they Haskell
| conventions?
| waych wrote:
| True Scotsman spotted!
| samatman wrote:
| Rust'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.
| BugsJustFindMe wrote:
| > _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 wrote:
| Python does have enums in its standard library
|
| https://docs.python.org/3/library/enum.html
| BugsJustFindMe wrote:
| Heh. 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 wrote:
| C++ added enum classes a while back, though.
| pkulak wrote:
| Go 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.
| seanw444 wrote:
| More efficient than a scripting language, less performant
| than a systems language. I really like it for anything
| network-related.
| kaashif wrote:
| It's all of the ergonomics of C combined with the bare metal
| performance of a garbage collector.
| samatman wrote:
| C does have enums. Not trying to detract from your point,
| which I agree with, but enums are definitely a thing, which C
| has.
| randomdata wrote:
| _> C does have enums._
|
| Then again, so does Go. Go doesn't have an enum keyword
| like C, but that isn't what defines enums.
| StevePerkins wrote:
| I would wager that the vast majority of backend software jobs
| are for people writing REST API microservices, exchanging
| JSON, with a mindset that is more practical and "blue-collar"
| than academic.
|
| Golang is an absolutely ideal language for writing REST API
| microservices, that exchange JSON, with a practical and blue-
| collar mindset.
|
| Plus it compiles to small-ish native executables. Which
| renders Docker superfluous in many uses cases, and also makes
| it well-suited for writing DevOps tooling (e.g. Docker,
| everything from HashCorp, etc).
|
| It's not trying to out-cool Haskell and Rust on online
| message boards. But I would never in a million years
| evangelize either of those two languages for routine REST API
| work in most real-world shops, whereas I could suggest Golang
| without losing professional credibility.
| tgv wrote:
| Completely true. And most of the time, I enjoy writing go.
| But the enums are weak. However, if the Go team wants to
| tackle an important addition to the language, it should be
| non-nillable pointers. No need for Option sum types, just a
| type annotation that says it cannot be nil (but can be
| assigned a nil type if you've tested it for not being nil).
| I've thought about building a linter (like the Uber one),
| but the way the types in the syntax tree are resolved makes
| it too complex for a small project.
| avgcorrection wrote:
| It's funny to emphasize the vibes of a language contra
| other ones when you're supposed to be on the supposedly
| pragmatic side. Haskell and Rust are popular on "message
| boards"? Better not mention them among my peers and risk my
| blue collar street cred.
|
| But it's a red herring in any case since enum types are
| such a basic programming language feature. No need to evoke
| the Cool Kids languages at all.
| pkulak wrote:
| > Golang is an absolutely ideal language for writing REST
| API microservices
|
| Those are strong words for a language with all the flaws I
| just mentioned. :D Yes, green threads are great for network
| programming, but it's not the only language with them, and
| one feature does not make it "ideal". If I had to pick the
| best networking language... I'd probably say Elixir.
|
| But even if we agree that it's ideal, it doesn't change my
| point.
| smw wrote:
| I find go distasteful, but are there really many other
| languages with an m:n threading model? Only other popular
| one I can think of is Erlang/Elixir.
| sgift wrote:
| I 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?
| klysm wrote:
| I think boneheaded is a good way to describe the evolution of
| go. It seems like the original authors were convinced most of
| the complexity of modern languages was unjustified and have
| slowly proven themselves incorrect over the years
| scosman wrote:
| Not a great take. The go team made a lot of decisions that
| weren't mainstream at the time, and nailed them. Fast builds,
| native binaries, language simplicity, new concurrent
| primitives, interface model, defer, no build flags, package
| system.
|
| Yeah it has evolved a bit since, but keeping the language
| simple is a worthwhile goal, so they didn't make rapid
| changes. It was intentional and thoughtful. If you want lots
| of language features, pick another language. I'll take my
| simple one.
|
| Also: go enums do suck.
| Jtsummers wrote:
| > new concurrent primitives
|
| You could make a case that concurrency primitives weren't
| mainstream in programming languages at the time. But
| there's not a strong case for saying that Go introduced
| _new_ concurrency primitives unless you just ignore the
| history of programming and programming languages. Nothing
| in Go 's concurrency model was new. Not quite mainstream,
| sure. But not new. [EDIT: By primitives I take you to mean
| built-in to the language, not brought in via libraries like
| pthreads or something.]
|
| I'd also question the statement that "native binaries" were
| not mainstream. That seems to ignore a _lot_ of code out
| there, including the C++ code that Go was (in part) meant
| to replace at Google.
|
| Defer as syntax is maybe new? But some form of finally
| construct was in a lot of languages used at the time Go was
| developed. Defer flattens the code by reducing indentation
| levels, but it introduced nothing new in terms of concepts
| that weren't already being used by programmers of
| mainstream languages.
| scosman wrote:
| Faiiir. Nothing was net new concept. But the package they
| made was quite unique. Garbage collected but always
| native. No thread access, native channels and coroutines
| instead. Defer is pretty much net new in language design
| terms. No while loop?!? "If err != nil"!!? Lots of bold
| ideas, in a good package, and it worked so well. Calling
| the evolution boneheaded dismisses how hard it is to make
| so many opinionated bets in one go, and still make
| something successful.
| Jtsummers wrote:
| Just pointing out that it was not really as novel as you
| seem to believe it was at the time it came out.
|
| > Garbage collected but always native.
|
| Ok, sure. There were no other native garbage collected
| languages. Ignoring history, this is true.
|
| > No thread access, native channels and coroutines
| instead.
|
| If we ignore history again, also new with Go.
|
| > Defer is pretty much net new in language design terms.
|
| I can't think of an equivalent in the form of syntax, so
| sure. This is a point to Go. It's a small change, but
| useful for flattening code.
|
| > No while loop?!?
|
| I don't know why the exclamation mark. They have one
| named type of loop with `for`, but they _definitely_ have
| a while loop: for x <= 10 { ...
| }
|
| That's a while loop, it's not an infinite loop, it's not
| a do-while loop. That they reduced their looping
| constructs to one name (and then determine which actual
| loop kind by what's between `for` and `{`) does not mean
| they actually removed while loops. This does simplify the
| syntax, maybe.
|
| > "If err != nil"!!?
|
| [edit: missed this one] if
| (some_c_lib_fun(...) == -1) { // check the errno
| }
|
| > Calling the evolution boneheaded
|
| I didn't. Why are you putting this here?
| klysm wrote:
| I totally agree that go nailed the head on a lot of things,
| including the list you provided (minus package system). I'm
| not convinced the evolution was intentional though from the
| start. From my memory the _attitude_ of the go ecosystem
| was that generics were not worth the complexity (for
| example). I don't have any concrete evidence of that, it's
| just the vibe I got from talking to folks about it.
|
| I also have great distaste for error handling in go but
| that's a distinct argument to have.
| mseepgood wrote:
| > convinced most of the complexity of modern languages was
| unjustified
|
| You don't think that most of the complexity of modern
| languages is unjustified?
| klysm wrote:
| I think most modern languages have complexity in the wrong
| areas. The type systems are not complex enough to capture
| the things I want algebraic data types with exhaustiveness
| guarantees).
| neonsunset wrote:
| Java generics aren't even proper generics. For that better look
| at Rust and C#.
| Zambyte wrote:
| In what sense? Because they only apply to non-primitive
| types?
| HideousKojima wrote:
| No, 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 wrote:
| That's a minor implementation detail that devs almost
| never have to think about.
| jimbob45 wrote:
| You'll run into it with generic arrays[0] in Java which
| are reasonably common.
|
| [0]https://www.baeldung.com/java-generic-array
| sgift wrote:
| As someone who works with Java all the time I'm the first
| to admit that something like TypeToken in GSON or
| comparable things in other libraries is not the greatest
| of things. I've also more than once wished I could to
| if(xy instanceof List<Something>), which you cannot do in
| Java. Can you work around it? Sure. Do I understand why
| Java has it? Yes. But "minor" .. no, it's not so minor in
| my experience.
| kriiuuu wrote:
| Haskell also has type erasure. It's an implementation
| detail, or are you saying that Haskell does not have
| proper generics?
| HideousKojima wrote:
| I don't know the specifics of Haskell's implementation
| but if it's mostly the same as Java's then yeah?
| kroltan wrote:
| I'm assuming because of erasure?
|
| In C#, List<T> and List<U> follows the same assignment
| rules as T and U, and at runtime are represented by
| distinct types. That means that going from List<T> to
| object to List<U> causes a runtime error at the point of
| casting.
|
| In Java, every generic type is erased to object at runtime,
| so the runtime type is just List, and you could cast
| List<T> to object to List<U> and only get an error later,
| when you try calling U methods on the contents of the list.
|
| (Yes in C# List is a concrete vector type and in Java it is
| a random-access collection interface, but that is not
| relevant here)
| blibble wrote:
| if T and U don't have the same erasure the compiler will
| forbid the cast
|
| if they do the compiler will warn you that an List<T> to
| List<U> cast is naughty
|
| but in that case the only methods you can call on it are
| that of the erased type anyway
|
| in practice I don't think I've ever seen a bug as a
| result of this type of erasure (and I've probably worked
| with at least several million lines of Java)
| layer8 wrote:
| The delayed error happens only when you ignore unchecked
| warnings, which would have been compiler errors if not
| for backward compatibility. One can turn them into errors
| with `-Werror`.
|
| The type erasure has occasional benefits, like allowing
| objects that are polymorphic in their type argument when
| that's still safe semantically (a simple example being
| emptyList() and emptySet()), where the type system isn't
| expressive enough to otherwise allow it. This is a bit
| like the "unsafe" escape in other languages.
| sgift wrote:
| To allow backward compatibility Java introduced Generics
| with type erasure, which in short means they only exist at
| compile-time, not at runtime (there are some hacks around
| that, which various devs have used with great success to
| still get the information). That is another reason to just
| start with Generics from the beginning if you design a new
| language, so you won't have compatibility problems when you
| introduce them. It's not like Generics were a controversial
| feature when Go came out.
|
| C#, which is often cited as an example for "generics done
| right" chose another path, which allowed generics at
| runtime - they made a hard break and just threw backward
| compatibility out of the window iirc. The reason Javas
| designers didn't do that is not only introduced generics
| far later in its lifecycle, but Java also has always
| followed the hard rule that breaking backward compatibility
| is something which should only ever used as a last resort
| and never between two versions directly following each
| other.
| layer8 wrote:
| IIRC Arthur Van Hoff, one of the original Java
| developers, actually advocated for the inclusion of
| generics in the initial version, but it was dropped due
| to time constraints. It's one of those features that a
| statically types language will always regret to not
| include from the beginning.
| neonsunset wrote:
| The "threw backward compatibility out of the window"
| happened in .NETFW 2.0, in 2005 (it did not, non-generic
| code that targets the pre-2.0 spec would work even today,
| the SDK is dead long ago but copied verbatim it would
| just run).
| Pet_Ant wrote:
| Everything in Java is a compromise. The leaders of Java will
| often admit that. Josh Bloch openly talks about Java being a
| working man's language, a blue collar one. Every idea has to
| be tampered down to make it fit for Java's purpose (Bloch has
| said that they blew Java's complexity budget on closures and
| wildcards). BGGA was the real proposal for adding closures to
| Java, but instead it went with CICE. Java works, and it's
| good enough to work in, and it's got enough jobs in it. But
| no one thinks it's the perfect language. There are no Java
| equivalents of C++ or Haskell die-hards.
| mseepgood wrote:
| > Generics? We don't need that
|
| You make it sound as though the developers were opposed to
| generics, which isn't accurate. Perhaps some in the community
| expressed such sentiments. The plan has always been to possibly
| include generics at some point, which they then did.
| mattrighetti wrote:
| > We don't need that .. (time goes on) .. okay, damn it.
|
| To prove your point further, at the time I got the impression
| that most of the community was against that decision and didn't
| see the point in introducing generics in the language.
| hardwaregeek wrote:
| Not even Java. The entirety of programming language
| development. Go is a language written by very good software
| developers but very bad language designers. It's an entire
| language of "why don't you just..." statements. Errors? Why
| don't you just return a value? Generics? Why don't you just use
| duck typing? Packages? Why don't you just use vendoring?
|
| In some cases these statements have some merit, but, as in most
| cases, they demonstrate that the authors didn't really do their
| homework before making a language. Or they willfully ignored
| all of these issues. I don't know which is worse.
| mseepgood wrote:
| > Generics?
|
| Go does have generics, though.
|
| > Why don't you just use duck typing?
|
| Go doesn't have duck typing. It has structural typing, which
| is not duck typing. Duck typing is dynamic typing (at
| runtime), structural typing is static (at compile time).
|
| > Packages?
|
| Go does have packages.
|
| > Why don't you just use vendoring?
|
| In Go it's recommended to use versioned modules, not
| vendoring.
| hardwaregeek wrote:
| It has generics _now_. It took them a solid 10 years to get
| it. Packaging took them what, 8 years? They had that
| ridiculous GOROOT stuff for the longest time. And
| interfaces are basically duck typed.
| zer0tonin wrote:
| > Go is a language written by very good software developers
| but very bad language designers
|
| That's exactly why I love it so much
| pjmlp wrote:
| Forget about Java, not even Pascal or C enumerations from 50
| years ago!
| ChuckMcM wrote:
| You cannot expect to get the Turing award for just creating a
| language that uses stuff already known and just changes the
| syntactic sugar.[1]
|
| To be explicit though, this comment is spot on. As someone who
| was part of the original Java development group when it was
| called "First Person Inc", I found the language "equivalence"
| concept debates the most interesting. For example, is Boolean a
| first class type? Or is it just a one bit integer? Is integer
| always signed? If you have 1 bit integers, 8 bit integers, 16,
| 32, and 64 bit integers, why not make 1024 bit integers a type
| too? Why is the number of bits fixed? If you want to be super
| radical, is it bits in an integer or is it _digits_? Is the
| integer type (radix, digits)? At one time there were
| discussions about real (signed), integer (unsigned), frac
| (fractional) and float (split).
|
| And then a product manager type walks in and says something
| like "Love the architectural purity y'all are going for here
| but nobody else uses all these things so let's not make
| something that is so complicated we'll never ship it."
|
| The author does a good job of exploring the characteristics of
| "good" enums, and I think it would be even better if it was
| understood that if your language is going to be used to
| implement finite state machines (which most programming
| languages do) then having strong protections against injecting
| invalid states into those machines is essential. If the
| language provides a way, that is great, otherwise you end up
| like the author did generating 30 - 50 lines of code for
| something that should take 3 - 5 lines to express.
|
| [1] This is an inside joke, IYKYK
| SamWhited wrote:
| I 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 wrote:
| > _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 wrote:
| > 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 wrote:
| I 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 wrote:
| The 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 wrote:
| Go 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 wrote:
| _> 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 wrote:
| The article is about enums, which are lacking in Go.
| randomdata wrote:
| No, Go definitely has enums. It is sum types (that some
| people have recently started calling enums) that Go lacks.
| grumpyprole wrote:
| Go does not model enums as separate types (like e.g.
| Pascal), they are essentially just integers like C.
| randomdata wrote:
| No, 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.
| grumpyprole wrote:
| Creating new types wrapping int is not really the same
| thing. It's not a closed set. Presumably one could define
| additional overlapping constants with the same integer
| type elsewhere?
| randomdata wrote:
| Go types do not support value constraints, no. That has
| nothing to do with enums, though. That's a different
| feature altogether.
| grumpyprole wrote:
| It has a lot to do with enums, especially if you are
| claiming statically typed enums. When defining a type,
| more often than not, we want to define the values that
| make up the set. For example, 'type boolean = true |
| false'
| randomdata wrote:
| > we want to define the values that make up the set. For
| example, 'type boolean = true | false'
|
| Sure, or, more relevant, `type monthOrdinal = 1-12` or
| `type email = {string}@{string}`. Any advanced type
| system will allow for that, of course, but Go does not.
| It does not even pretend to claim to be an advanced
| language. It has, quite explicitly, chosen to not be.
|
| Yes, you are right that if Go had value constraints then
| an enum type could utilize those constraints, but, again,
| nothing to do with enums themselves. You are confusing
| unrelated features.
| grumpyprole wrote:
| > You're confusing unrelated features
|
| Actually I think you are. For example, almost all
| statically typed languages since Pascal do not have value
| constraints but support typed enums as closed sets.
| There's no advanced type system needed - no need to
| define enums as integers and then put additional
| constraints in the type system to try and restrict this.
| There is also no need to model enums as integers in the
| type system in order to use integers as a runtime
| representation.
| randomdata wrote:
| You can't have closed enums without value constraints.
| Yes, some languages have been lazy and provided value
| constraints only for enum types.
|
| Which is an interesting choice: Give a noose for
| developers to hang themselves with for every single other
| type other than enums - the types they are going to use
| more often - and not think twice, but then go full on
| helicopter parent when using enums - the one type that
| isn't particularly interesting.
|
| It's a cool parlour trick, don't get me wrong, but I
| guess that's why almost all of the _popular_ statically
| typed languages since Pascal never bothered and left
| enums open. They put their time into features that
| actually mattered to developers instead.
| adamrezich wrote:
| is there any further information on why Rust and other recent
| languages have started using `enum` to refer to sum types? I
| don't use Rust or TypeScript (edit: apparently TS doesn't
| have this, my memory is bad) or any of those languages and
| it's been very strange to see this redefinition occur
| randomdata wrote:
| Typescript has actual enums. They behave just like Go's
| (for better or worse).
|
| It's not clear why Rust got confused.
| adamrezich wrote:
| yep, sorry about that, I misremembered TypeScript as
| having the same "kind of enums" as Rust.
| Quekid5 wrote:
| I think it might be due to the O'Caml influence on early
| Rust. They call them enums there[0].
|
| [0]
| https://www.ocamlwiki.com/index.php?title=Enums_in_OCaml
| ReleaseCandidat wrote:
| They are called variants in OCaml (and inductive data
| types in Roqc).
|
| But way more important: this OCaml wiki is an AI
| generated mess full of, well, bullshit.
| https://discuss.ocaml.org/t/whats-up-with-ocamlwiki/13605
| samatman wrote:
| My best guess is Java.
|
| Edit: I guess if you've never seen that this is, uh,
| controversial. Or something. Anyway, Java enums are full-
| strength classes, look at the planet example here https://d
| ocs.oracle.com/javase/tutorial/java/javaOO/enum.htm...
|
| This is more like a Rust enum than a C one, I think you'll
| find.
| trealira wrote:
| Maybe to appeal to C and C++ developers. Rust makes its
| syntax superficially similar to C/C++ syntax in many other
| ways: pointer/reference syntax, declaration of "struct"
| types, generic types using <T>, curly brace block
| structure, and the naming conventions enforced by their
| lints. To be fair, many of these traits of C and C++ are
| also copied by other programming languages (e.g. curly
| braces). But they could have gone in a different direction
| and had pointer and record syntax more like Pascal, or made
| a syntax more like OCaml, Standard ML, or Haskell.
| hgs3 wrote:
| > The title is clickbait.
|
| The article is an advertisement for the authors own Go package
| that addresses the "problem."
| grumpyprole wrote:
| Using integers to model optional behaviour sucks, whatever you
| choose to call it. It's what one does in assembly language and
| C. Even Pascal from the early 70s had type-safe enumerations.
| coldtea wrote:
| Go'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 wrote:
| I 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 wrote:
| > _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 wrote:
| I 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.
| richbell wrote:
| > I 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.
|
| I can't tell if this is sarcasm.
|
| If it isn't, I don't see how what you've mentioned is
| minimalistic or how adding an option type would be
| against Go's identity.
| DinaCoder99 wrote:
| Why use ints in the first place if what you really want is
| strings?
| Steltek wrote:
| Integers are far faster, smaller, and golang is strongly
| typed. Once you're past the serialization phase, strings
| have many disadvantages.
| IshKebab wrote:
| > rigid and encompassing
|
| Rigid makes it sound bad. I would suggest "reliable" instead.
| mirekrusin wrote:
| ...also:
|
| * no null safety
|
| * working with errors
|
| * poor type system
| mseepgood wrote:
| > Go's iota is probably one of the worst ideas in all
| programming languages.
|
| iota is a great idea, especially if you have to define bit mask
| constants, e.g. 1 << iota. I wish other languages (yes, also
| those with enums) had it as well.
| taeric wrote:
| A 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 wrote:
| I'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 wrote:
| I 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 wrote:
| Yeah, 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 wrote:
| I 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.
| zer00eyz wrote:
| >> 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.
|
| I had the joy of a numeric ID (an int) getting a B added to the
| end of it to distinguish the product as being the "same" but
| sourced from another vendor... (and vendor is part of the
| system so this is layers of silly, but useful on the floor if
| there is a problem).
|
| This is the down side of "fleets" of applications with
| differing degrees of type safety working in concert.
|
| Should an enum be fixed, and its change need to be reflected in
| every system? Cause current go out of the box isnt that. Should
| it be open ended as your suggesting ... because a "new int
| value" can flow through the system in an "Unsafe" way depending
| on the permissiveness or quality of your code.
|
| I dont like any of the answers, but I candidly dont have a lot
| of problems with the current enum system in go. Is it great,
| no. But if your validating at your borders/boundries (storage,
| api, etc) and being responsible I dont really give it much
| thought.
| taeric wrote:
| I found this lesson out the hard way by finding that a new
| "status" had been added to our service and blew up some of
| our monitoring code. Since our monitoring code was still
| correct for the cases that they were checking for, and had
| default clauses to note things that were not relevant, we
| were crashing purely because I had coded it to convert the
| incoming value into a java enum. Oops...
| Brian_K_White wrote:
| well thank you for the example anyway :)
| kevmo314 wrote:
| > 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 wrote:
| > 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
|
| It's very easy to create an enum value that isn't actually in
| the defined range
| mseepgood wrote:
| Can 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 wrote:
| You struggle to imagine a programmer passing a value of the
| wrong type to a function?
| bb88 wrote:
| Or passing a wrong value and the compiler allows it
| because the programmer trusted the compiler to "always do
| the right thing".
| mathiasgredal wrote:
| If 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.
| reactordev wrote:
| "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 wrote:
| It 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 wrote:
| It is basically Limbo with updated syntax, and AOT instead of a
| JIT.
| andreimackenzie wrote:
| Simplicity 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 wrote:
| Simplicity 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.
| geodel wrote:
| Very disappointing.
|
| Disappointing it maybe but productive it is not considering
| people can easily move to far better languages.
| bedobi wrote:
| You're right.
|
| Go is not simple, it is idiotically designed to deliberately
| exclude common sense features that ironically makes it less
| simple and more error prone to code in and read Go.
|
| Other languages are objectively better than Go for every
| imaginable use case. Rust is better for embedded. Kotlin is
| better for back end. I could go on.
|
| The creator of Go is very open and candid that he thinks his
| target audience, Google Engineers, are too stupid to use
| "advanced" features like oh I don't know, sane error handling?
| and any number of basic things other languages have.
|
| I know how cringe it is to start flame wars about programming
| languages, but srsly, Go, PHP, Perl, JS and a few others really
| are objectively worse (for _every context and use case_ ) than
| widely used alternatives.
| yhavr wrote:
| > sane error handling?
|
| Golang _has_ sane error handling. It just considers errors a
| normal and expected situation.
|
| When you perform a http request, and the result is successful
| you expect the result to be assigned a variable, right? Then
| why would you expect non-successful outcome to be returned in
| a different way? Why is it different? Why do you unwind the
| stack? Something terrible happened? Definitely not, it's as
| real life as 200 OK.
|
| For unrecoverable things golang has panics, and if you don't
| like the idiomatic way of handling errors, you can just throw
| them like exceptions.
| bedobi wrote:
| 100% agree, and Go gets oh so close
|
| But the correct and only sane way to do this is
| Either<Error, Success> that you can then pass on, map over
| both or either of the two, flatMap to chain with other
| Eithers, fold into a single thing etc etc. Not endless
| sprinkling of
|
| if err != nil { log.Fatal(err) }
|
| everywhere (and no, those operations are not obscure,
| esoteric or difficult to learn or understand - they're the
| same for other types like Option, List etc and are trivial
| to learn in a day for people who aren't familiar with them)
|
| + not making the compiler distinguish between null and non-
| nully values (as eg Kotlin, Rust and Haskell does) in
| itself as well is inexcusable for a modern language
| uticus wrote:
| What language typically returns the
| `Either<Error,Success>` you refer to here? I get (and
| love) the idea but have never seen it in official
| documentation (sure I could go off the beaten path and
| implement in my language of choice).
|
| Also, did you come up with this on your own, or were you
| exposed to it?
| oasisaimlessly wrote:
| * Rust: https://doc.rust-lang.org/std/result/
|
| * Haskell: https://hackage.haskell.org/package/base-4.19.
| 1.0/docs/Data-...
|
| * C++:
| https://en.cppreference.com/w/cpp/utility/expected/value
| LispSporks22 wrote:
| > Why do you unwind the stack? Something terrible happened?
| Definitely not,
|
| Definitely do.
|
| In Go we just have to emulate it, badly, by manually
| writing code to forward the error up the stack so you can
| finally top-level print "error bad thing happen" or maybe
| some unholy stringification of wrapped errors possibly
| collected along the way.
| pjmlp wrote:
| Pascal in its original 1970's design, type
| myEnum = (value1, value2, value3, value4)
|
| Naturally that is too advanced and slows compile times.
| leosanchez wrote:
| > Naturally that is too advanced and slows compile times.
|
| Sarcasm ?
| pjmlp wrote:
| On the spot.
| mseepgood wrote:
| Wirth 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 wrote:
| I 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 wrote:
| You can disagree with design philosophies, but Wirth and
| the Go designers probably have thought more about these
| things than you.
| pjmlp wrote:
| Appeal to authority.
|
| Also note that none of Wirth's Oberon variants have
| achieved any commercial success.
|
| Modula-2, which had enums, on the other hand did enjoy a
| limited success, across UNIX, PC and Amiga, and is
| nowadays even available as standard GCC fronted.
|
| Go would have been a failure if the authors weren't
| Google employees, like it happened with their Limbo.
| zoogeny wrote:
| > enumerations give rise to the exceptional rule that the
| import of a type identifier also causes the (automatic)
| import of all associated constant identifiers
|
| I am confused by this assertion. I mean, if I had a module
| `Source` defining an enum: type MyEnum =
| (value1, value2, value3, value4)
|
| and another module wanted to import it:
| import ( MyEnum ) from "Source"
|
| I don't see how I have automatically imported all of the
| associated constant identifiers. Unless he was assuming that
| this would force me to import `value1`, `value2`, etc. as
| distinct identifiers? But it seems these ought to be
| namespaced inside `MyEnum`, e.g.
| MyEnum.value1
|
| And one could easily imagine an import syntax to selectively
| import Enum values if desired: import (
| MyEnum: ( value1, value3 ) ) from "Source"
|
| Of course, I'm just making up syntax, but I hope the meaning
| is clear.
| jurschreuder wrote:
| You 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 wrote:
| Enums 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 wrote:
| Anyone 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 wrote:
| https://github.com/goplus/gop, but they go slightly too
| overboard imo.
| pjmlp wrote:
| They already exist, D, C#, Odin, Nim... no need to insist when
| the community is so down into minimalism.
| bmoxb wrote:
| That 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 wrote:
| I use this one https://github.com/abice/go-enum
| lantastic wrote:
| I 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#...
| llmblockchain wrote:
| It'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 wrote:
| They complain that Go has no usable, type safe enum and then
| show a way to build them in Go.
| zarldev wrote:
| DSL? 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 wrote:
| Yes. 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 wrote:
| But all go generate does is run a binary like stringer?
| This can be used in go generate.
| zer00eyz wrote:
| >>> 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 wrote:
| Or just define the constants as string constants in the first
| place: "type Operation string"
| zoogeny wrote:
| I 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 naive 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
| mcphage wrote:
| This is the "you're holding it wrong" of linguistic arguments.
| zoogeny wrote:
| To 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 wrote:
| I'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.
| avgcorrection wrote:
| "Enumeration" according to a handy dictionary: "the act or
| process of making or stating a list of things one after
| another"
|
| I am--for some reason--reminded of philosophers who go looking
| for metaphysical problems, disputes, etc. where there _aren't
| any_.[1]
|
| https://en.wikipedia.org/wiki/Quietism_(philosophy)
| MarkSweep wrote:
| So I guess you have a point that since Go does not even have
| enum's as a formal concept, they can't suck. But this presumes
| that the only context where you can criticize Go is the context
| that only contain's concepts expressible in Go. This is
| basically saying "Go does not have enums, therefore it cannot
| be criticized for lacking enums".
|
| In the wider context of programming languages, enum is fairly
| well defined concept. Features like being able to convert a
| value to a string and do exhaustive checking on switch
| statements are widely implemented. The iota feature in Go is
| clearly imitating C's enum keyword. It is fair to compare Go's
| built-in ability to declare an enum-like type against other
| language's ability to declare the concept.
|
| To be clear, I'm not saying every language has to have every
| feature. I'm just saying the lack of a feature in a language is
| not a sufficient reason to excuse its lack.
| marklar423 wrote:
| If 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. 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 wrote:
| enums are handled poorly in so many language. very confusing.
| skybrian wrote:
| The 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 wrote:
| It'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
| brabarossa wrote:
| >> 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
| divan wrote:
| Not 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 wrote:
| func (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 wrote:
| They don't use linters either.
| code_runner wrote:
| I 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 wrote:
| A 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 wrote:
| This 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 wrote:
| There is one simple hard fact: shorter code is faster to read
| and understand, period. If someone doesn't understand what
| the result of `o != Unknown` is then they should probably go
| back and read more about programming. Sorry to sound a bit
| condescending.
| adam_arthur wrote:
| Most people aren't good programmers. And most also don't make a
| concerted effort to improve.
|
| Sorry to say, but that's the simplest truth. Many go years and
| years in this industry with little improvement to
| quality/succinctness/readability. If you cared, and tried, you
| would.
|
| Programming skill is uniquely distinct from domain knowledge,
| by the way. e.g. all of the research code written by domain
| experts that is full on spaghetti.
| zarldev wrote:
| Hey did you read to the bottom of the article. It shows that
| that was just a temporary solution, the real invalid code is
|
| func (t operation) IsValid() bool { _, ok := strOperationMap[s]
| return ok }
|
| Tbh it has been updated to us an array instead for string
| indexing.
| t0astbread wrote:
| What 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.)
| 015a wrote:
| `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 wrote:
| > 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
| ilove_banh_mi wrote:
| Ada 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...
| sydbarrett74 wrote:
| Ada rocks. It's just about the most underappreciated language
| ever.
| pipeline_peak wrote:
| This feature that technically doesn't exist sucks
___________________________________________________________________
(page generated 2024-03-01 23:02 UTC)