4 ms·
Mainly, there are 3-4 reasons for considering alternatives to using booleans: 1. Booleans are primitive types without rich domain-specific information or conte
by nivertech 3y ago
Mainly, there are 3-4 reasons for considering alternatives to using booleans:
1. Booleans are primitive types without rich domain-specific information or context. When using true/false, it can be more beneficial to employ more descriptive options such as AnsweredYes/AnsweredNo, PositiveResult/NegativeResult, LightsOn/LightsOff, or UserOnline/UserOffline. Mixing and matching booleans of different domain types can lead to errors that the compiler may not catch. For instance:
bool isLightsOn = true;
bool isUserOnline = false;
bool isAdmin = true;
if(isLightsOn && !isUserOnline && !isAdmin) {
...
}
compare with Elixir:
case {lights_status, connection_status, user_role} do
{:lights_on, :user_offline, :unprivileged_role} -> ...
...
end
The example above is basically a Decision Table [1].
While booleans can still be used in system code or libraries, it is advisable to avoid them in application code.
2. Preemptive pluralization [2] makes it easier to refactor code. Going from two options to three is simpler than transitioning from one option to two. Consider the following examples:
bool isLightsOn;
or
type LightsStatus = Bool
vs
type LightsStatus = LightsOn | LightsOff
Suppose there is a new requirement to support dimmed lights. In the former case, significant code refactoring is necessary. In the latter case, adding a new option to the sum type is straightforward, and the compiler assists in handling the changes:
type LightsStatus = LightsOn | LightsOff | LightsDimmed
Similarly, let's examine another scenario:
boolean isOnline;
or
type ConnectionStatus = Bool
vs
type ConnectionStatus = Online | Offline
The latter option can easily evolve into:
type ConnectionStatus = Online | Offline | BadConnectivity
and later:
type ConnectionStatus =
| OnlineMobile
| OnlineFastInternet
| Offline
| BadConnectivity
| IntermittentConnection
| ConnectedToMeshnet
3. Booleans encourage the overuse of conditionals and branching, including nested branching. This can increase the cyclomatic complexity [3] of the code. It is important to manage cyclomatic complexity to keep the codebase maintainable and easier to reason about.
Also see "Get rid of those boolean function parameters" [4].
4. Booleans can indicate whether something occurred, but they lack both the necessary context and the timestamp indicating when the event took place. However, by transforming booleans into nullable or optional timestamps, one can achieve a rudimentary implementation of Domain Events or Event Sourcing.
user_connected_at: nullable(Timestamp)
lights_turned_on_at: nullable(Timestamp)
record_deleted_at: nullable(Timestamp)
NOTE: I'm not advocating for the pattern above, but it's widely used because of the limitations inherent in booleans.
--
References:
[1] https://en.wikipedia.org/wiki/Decision_table https://en.wikipedia.org/wiki/Decision_table
[2] https://www.swyx.io/preemptive-pluralization https://www.swyx.io/preemptive-pluralization
[3] https://en.wikipedia.org/wiki/Cyclomatic_complexity https://en.wikipedia.org/wiki/Cyclomatic_complexity
[4] https://mortoray.com/get-rid-of-those-boolean-function-parameters/ https://mortoray.com/get-rid-of-those-boolean-function-param...
- mrkeen 3y agoThat's great, you just explained why ADTs are superior to booleans. My question was why are ADTs the same as booleans?
- nivertech 3y agoI didn't wrote that ADTs are the same as boolean, only that optional types (Maybe/Option) are the same. For 2 reasons: 1. the 2-case, so it will be harder to refactor once you add additional cases 2. they're missing domain/context/intent (Why value is missing?) So, Maybe/Option is an improvement over null/nil and boolean flags, and ADTs/FSMs are improvement over Maybe-s.