5 ms·
The problem isn't so much the lack of enums, it's more that there is no way to do exhaustive pattern matching at compile time. I have seen production systems go
by weavie 2y ago
The problem isn't so much the lack of enums, it's more that there is no way to do exhaustive pattern matching at compile time. I have seen production systems go down because someone added a variant to an 'enum' but failed to handle that new variant everywhere.
- dgb23 2y agoIsn’t that the first thing you would do if you add a new variant?
- K0nserv 2y agoOnly if you remember, which you, or someone else, is bound to not eventually
- rty32 2y agoadded that every switch/if should handle this exhaustively. For any project with more than a few dozens of files, it is basically impossible to remember all the downstream code that uses the enum -- you have to track it down, or better, let compiler automate check all usages
- dgb23 2y agoBut you're not adding variants without a reason? You want them to have some effect. It's hard for me to think of an example where it would make even sense to "having to remember to handle the variant" rather than "handling the desired effect of the variant".
- K0nserv 2y agoPeople are stressed, get distracted, are tired, don't have complete knowledge etc. This is kind of like arguing that null pointers aren't a problem, you "just" have to check all usage of the pointer if you make it null. In practice we know solutions like this don't work
- weavie 2y agoHuge codebase. It was handled in 13 places, but they missed the 14th.
- aleksi 2y agoThere are two linters that add those checks: https://github.com/nishanths/exhaustive https://github.com/nishanths/exhaustive for values and https://github.com/alecthomas/go-check-sumtype https://github.com/alecthomas/go-check-sumtype for types. Both are integrated into golangci-lint. I use both a lot and have only a positive experience. Having support from the language would be nice, though.
- tialaramex 2y agoYeah, it makes a huge difference whether this is the default, and that's not a Go thing, it's not a programming language thing, it's the whole of human existence. Literacy is an example. We're used to a world where it's just normal that other humans can make marks and interpret our own marks as to meaning. A thousand years ago that would be uncommon, ten thousand years ago vanishingly rare. I really celebrate tools which take an idea that everybody agrees is a good idea, and bake it in so that everybody just has that, rather than agreeing it's a good idea but, eh, I have other things to do right now. And it annoys me when I see these good ideas but they're left as an option, quietly for a few people to say "Oh, that's good to see" and everybody else misses out, as if "Literacy" was an optional high school class most of your peers didn't take and now they can't fucking read or write. Example: Git has a force push feature, necessarily we can (if we have rights) overwrite a completely unrelated branch state, given any state X, now the state is our state Y instead. This isn't the default, that part is fine... Git also has "force-with-lease". This is a much better feature. Force-with-lease says "I know the current state of this branch is X, but I want to overwrite it anyway". If we're wrong, and X is not (any longer perhaps) the current state, the push fails. But force-with-lease isn't the default. [Edited to fix clumsy wording in the last sentence]
- dizzyVik 2y agoThese both look like great tools. I'll give them a spin!
- K0nserv 2y agoThe overarching lesson of my career has been that people are fallible and processes that rely on humans to not make mistakes are bound to fail. Recently I've also been thinking about the importance of being able to reason "locally" about code, it might be the single most important property of a software system. "locally" typically means a function, class, or module, but I think it can also be applied to "horizontal" cases like this. For example, if you add an enum variant the compiler should guide you to reason about each place where the enum is no longer exhaustively matched.
- ReleaseCandidat 2y agoIn theory yes. But practically there are always locations where we can't match every case, so we either have to live with a warning or add a catch-all arm. And as soon as a catch-all arm exists, we are in "not checked any more" state, but with a compiler that is supposed to check for exhaustiveness. Which is way worse if the catch-all arm isn't a `panic("Unhandled match branch!")`. Yes, you can counter that with GADTs and match against exhaustive subsets. But to ergonomically handle these cases, you need something like Pattern Synonyms or you drown in boilerplate.
- gray_-_wolf 2y agoAs long as the set is limited (as enums usually are), you can always go with the middle ground between omitted case and catch all: case FILE_NOT_FOUND: /* This is normal, nothing to do. */ break; This should still catch adding new value into the enum and not handling it.
- ReleaseCandidat 2y agoI should have added that this solution stops working as soon as you're handling 5 out of 50 cases. Lexical tokens are which always trigger the mentioned problems in my code - often you match against subsets, are there are _way_ too many of them to add them all explicitly.
- K0nserv 2y agoYou don't have to add a catch-all arm, you can still explicitly match i.e match value { A => { do_stuff() }, B => { unreachable!("B is not applicable here because <reason>") }, } now when you add C you still have to match it(given the enum is exhaustive).
- ReleaseCandidat 2y agoYes, I see, I should have mentioned that. Same as above: this solution stops working as soon as you're handling 5 out of 50 cases (or, more realistically, 10 out of 200). Lexical tokens are which always trigger the mentioned problems in my code - often you match against subsets, as there are _way_ too many of them to add them all explicitly.