| ▲ | kccqzy 2 days ago |
| Nobody says you have to have a single enum type containing all the combinations. Chances are, you can use sum types (discriminated unions) to factor things nicely if you think about them. For example if option B is only relevant when option A is set to true, you can have something like data OptA = ATrue OptB | AFalse
data OptB = BTrue | BFalse
There are three valid combinations but no type has three alternatives. Nobody in their right mind would write out an enum with 2^16 cases. If you do, you are misusing enums and it's time to consider other tools in your language. |
|
| ▲ | joe_the_user 2 days ago | parent | next [-] |
| Nobody says you have to have a single enum type containing all the combinations. No, no one would continue up to 2^16 and the code would get unmanageable long before that. But it's illustration of the problems starting out dealing with the invalid states of two variables using an enum because what happens when more and more variables arrive? Sure, the standard answer is "just refactor" but my experience is no client or boss wants to hear "adding this small state is going require a lot of change" and a trickle of binary conditions is a very common occurrence as is code expanding to handle these (and become excessively complicated). Chances are, you can use sum types (discriminated unions) to factor things nicely if you think about them. Maybe you have a good chance of combining these binary conditions in a good way. But I mention you've substituted a hard problem instance (factoring binary conditions) for an easy problem instance (checking binary conditions). Functional programming has a weird dual personality where on the one hand you hear "A functional programmer is always a smarty and solve hard problems as a matter of course" but also you hear "functional programming would be the dominant paradigm if only ... we taught people young so they wouldn't have their bad procedural habits" |
| |
| ▲ | BoiledCabbage 2 days ago | parent [-] | | > and a trickle of binary conditions is a very common occurrence as is code expanding to handle these (and become excessively complicated). But you still have to handle this in your code. Wherever you have your conditions that handle this, your nest of if statements still need to cover all of these invalid combinations and ensure your app doesn't silently do the wrong thing or just crash (better). Changing requirements requires changing code. I don't think it's a valid argument to say "we shouldn't take that approach because as requirements change we'll have to change the code". That's essentially software development. Practically if you don't want to use enums and want another option, use a "builder" object. Pass in all of your booleans there and have it do you validation when you call its build method. It returns a read only configuration that the rest of your system can use, and the build method fails if an invalid combination of flags are passed in. Again you force only valid combinations to exist after you call "build". And all code relies on the config produced by the build method. | | |
| ▲ | joe_the_user a day ago | parent [-] | | Changing requirements requires changing code. I don't think it's a valid argument to say "we shouldn't take that approach because as requirements change we'll have to change the code". That's essentially software development. You're misunderstanding me. Of course changing requirements mean changing code. The distinction is between a situation of "a small change in a requirement means a small change in code" and "a small change in a requirement means a BIG change in code". The make "make an enum of all the legal cases" approaches produces a situation where adding more binary conditions and requirements around them resulting in each change resulting a larger increase in code. And this in turn can result in a "we have to refactor this if we get one more change" and that's even more frustrating to those dictating requirements (and not uncommon in enterprise software). Practically if you don't want to use enums and want another option, use a "builder" object. Pass in all of your booleans there and have it do you validation when you call its build method. Cases are fine in some circumstances, enums of legal cases are fine in other circumstances and these builder might even be useful in a few bizarre cases. The main argument I'd have is that when one passes from "this approach can be useful, let's look at the situation" to "anything without this is bad", you often wind-up with a complete misallocation of resources and a fragile inability to make changes, as can be seen in today's legacy code (which was often yesterday's "best coding practices code"). | | |
| ▲ | BoiledCabbage 13 hours ago | parent [-] | | I still feel like there is an important point you're walking past. If there are 2^16 different combinations that are relevant, then you still need to handle these 2^16 combinations in your code. If you're ready a configuration file from a user and only different subsets of them are valid, somewhere in your code you still need all of the complexity to let the user know they have passed in an invalid combination. And all of that logic is equally or more complex than an enum. If all of those cases can be handled by a few simple "if" in your code, then you'll have only a few valid options in your enum. If you have a ton of valid options you need to list in your enum, then you'll have a tone of cases you need to handle in your code. Your underlying complaint to me sounds like you've received a lot of complex cases via your requirements. But either way the complexity is there in your code regardless of whether you validate it upfront in an enum, or deep in our code base. |
|
|
|
|
| ▲ | jandrese 2 days ago | parent | prev [-] |
| Imagine a case where you have 4 options. W, X, Y, Z. Y and Z are mutually exclusive. X can only be set if W is set. If Y is set then X must be set. Going down this road you end up encoding your business logic into your datatypes. Which is good to a degree, but makes things messy when new options are added or requirements change. Imagine a new option U is introduced that is only valid when W is unset and Z is set but allows X to be set. Your interface becomes very hard to manage with even a small amount of complexity added to the options. |
| |
| ▲ | kccqzy 2 days ago | parent | next [-] | | This is an instance of inherent complexity. Your domain is complex. You either place them into a series of nested if statements (which is what majority of programmers do), or you place it into the type system. You cannot avoid complexity either way. We are merely arguing where this complexity belongs. Such complexity is hard to manage in either case. | |
| ▲ | aiono 2 days ago | parent | prev [-] | | What is the alternative? And is it really better than encoding into the data type? Only option I can think of is writing unit tests for each case, which doesn't seem like too much different. And without type encoding you never sure that invariant always hold. You can always manipulate any options in any part of the program. Then after any modification you have to perform a "sanity check" if options are in a well defined state or not. I don't see how this is better than encoding the invariant into the types. |
|