[HN Gopher] Ask HN: Do You Use Enum for Yes, No, Unset?
       ___________________________________________________________________
        
       Ask HN: Do You Use Enum for Yes, No, Unset?
        
       I've seen different arguments on this topic and thought I'd ask HN
       if you all have a standard for this.  Say you have a form that's
       filled out and saved to a DB. One required field is a dropdown with
       Yes and No. But the field begins as blank to show that a value
       hasn't been selected yet, ensuring that the User pays attention to
       the field.  Do you use a Nullable Bit (DB) and Nullable Bool
       (Code)? OR an Enum: Yes, No, Unset?  You wouldn't use a boolean to
       represent states like say New, Processing, Done. But do you
       consider NULL a separate "state"?  Thanks
        
       Author : whitakerch
       Score  : 16 points
       Date   : 2022-06-17 19:38 UTC (3 hours ago)
        
       | dathinab wrote:
       | In some cases I encode the enum in the field name like
       | `isThisOrElseThat BOOL` if you have clear opposites it often
       | doesn't make sense to do so, but if it's non clear opposite
       | either A or B choices or nullable and NULL isn't equal to false
       | it can be helpful.
       | 
       | It also is annoying to use.
       | 
       | So if the DB supports a reasonable overhead enumeration that's a
       | good choice, too (but that's not always the case, e.g. sqlite).
        
       | marginalia_nu wrote:
       | I'd probably avoid using NULL in DB to convey a borderline-value
       | for the simple reason that NULL has weird semantics in SQL and it
       | can be pretty confusing.
       | 
       | If you're not using NULLs in DB, it's weird to translate an enum
       | into a nullable Boolean.
       | 
       | In the end, there is very little downside to using enums in this
       | case. It's by far the least confusing option.
        
       | SigmundA wrote:
       | There are two common "nulls" in this situation, the "unset" one
       | and the "unknown" one. Unset would imply no one has ever set the
       | value, unknown means someone has set the value to unknown which
       | is sometimes an important distinction.
       | 
       | Javascript this is easy undefined can represent unset, and null
       | can represent unknown. This is very useful for updating the state
       | as well, undefined means do not change the value while null means
       | update the value to unknown.
       | 
       | I have used code (enums) for yes/no/unknown that allow null value
       | for unset as well in relational db's. If I had a choice I prefer
       | JavaScripts data model of a boolean that can be undefined or null
       | as well.
       | 
       | A nullable boolean in general has been better for a tristate than
       | enums, but like I said many times I need a quadstate.
        
       | dvh wrote:
       | I use integer surrogate keys everywhere. So 1 yes, 2 no, 3 unset,
       | 4 yes but no every other Friday, 5 use this option for customer
       | x, 6 no with notes, 7 zebra, 8...
        
         | marginalia_nu wrote:
         | With some 4.2 billion integers to choose from, you can pretty
         | much put everything in one enum. Genius!
        
         | Volundr wrote:
         | I'm pretty sure I've worked on this codebase...
        
       | disintegore wrote:
       | Yes/no/unspecified is a good match for something like
       | `Option<bool>` or `bool?`. Their usage covers every case and
       | nothing needs to be elucidated.
       | 
       | With that said, in a language that supports null and has no
       | compile-time mechanism to detect potential null dereferencing
       | (like modern C# for instance) I might use an enum.
        
       | adzm wrote:
       | For SQL there is usually an optimization where nullable columns
       | are simply a bit set; same with boolean/bit columns. This does
       | sound like a good use of a nullable bit.
       | 
       | You may want to distinguish things further in the future such as
       | adding a new column and you can determine if it was
       | unset/undefined by the user versus null because the user never
       | saw it. In that case an enum makes sense.
       | 
       | Personally I think the logic maps directly to nullable bit.
        
       | IceMetalPunk wrote:
       | JS/TS dev here. I tend to stick with a nullable boolean. For me,
       | "no value" is usually pretty equivalent with "null" in general,
       | and the data type I choose to use depends only on the non-null
       | possibilities.
        
       | olliej wrote:
       | You could be using Objective-C with NSBoolean, so you have @YES,
       | @NO, and nil :D
        
       | rvdginste wrote:
       | I use the convention that an 'null' value is a value that was not
       | explicitly set by the user.
       | 
       | So in C#, I would always use a nullable boolean in this case
       | (choices yes and no). If the field is a required value, I would
       | annotate the field as such.
       | 
       | When using the field in an ORM, the required annotation would
       | lead to a not-null field in the database.
       | 
       | When using the field in a dto, the required annotation could be
       | used for validation of incoming input.
       | 
       | If the user should have the choice "yes", "no", "unknown", I
       | would use a nullable enum to express this. An 'null' value means
       | that the value was not explicitly set. The enum value "unknown"
       | indicates that the user explicitly chose the "unknown" value.
       | 
       | So, in both cases the 'null' is not a separate state, it simply
       | indicates that no explicit value was given to the field.
        
       | DubiousPusher wrote:
       | I work in a slightly different context but have dealt with a
       | similar problem. I build Unity3D apps and I write a lot UI for
       | the editor that affects how things are serialized. I've stopped
       | serializing enums due to being burned too many times. When
       | people:
       | 
       | -Add entries not at the bottom -Rename entries -Remove entries
       | -Rearrange entries
       | 
       | So instead I made a kind of data-driven enum that you create
       | through the editor. Each entry (called a key) in the "enum" is
       | backed by a guid. You can associate a name with each key.
       | 
       | When you want to save info like on/off/unset. You create an enum.
       | Then you declare a key in your data model. The UI will then show
       | that key as a drop-down with the mapped names as the choices. But
       | in the end, the associated guid is what is recorded.
       | 
       | Not only does this largely solve the, add, rename, re-arrange
       | problems, it solves some other persistent issues as well. I've
       | gotten designers to pickup this tool so they stop using ints and
       | string to signal events. This way, they define their signal once
       | as a data-driven enum and then they see it as a choice throughout
       | the app rather than as something they have to type in matching.
       | 
       | It also causes engineers to write components which are more
       | configurable for designers. Instead of checking a literal token
       | like:
       | 
       | if (currentEvent.appState == MyAppStateEnum.Start) doSomething()
       | 
       | They declare the Key and check its value: public Key
       | appPhaseToDoSomething;
       | 
       | if (currentEvent.appState == appPhaseToDoSomething) doSomething()
       | 
       | Since appPhaseToDoSomething is exposed as a dropdown in the
       | editor, it means a designer can change the phase when something
       | might happen without an engineer. And engineers basically have to
       | write in this modular way because there's no token to check.
        
       | jeroenhd wrote:
       | I've seen some terrible implementations in Java abuse Boolean as
       | a tri state value: true, false, null.
       | 
       | However, this always caused bugs because you'd end up with
       | terrible code patterns. For one, what does "unset" mean? Does it
       | equal false, because it wasn't enabled? What's the default? I
       | think "unset" is a state to be generally avoided because it's
       | bound to create problems for you down the line.
       | 
       | Personally, I prefer enums for anything that's not an actual
       | boolean. A boolean should, in my opinion, always be a yes/no
       | variable. Other states should probably be more specific.
       | 
       | I've seen people use booleans and other types to represent
       | limited options (i.e. animal.IsCat to determine if an animal is a
       | cat or a dog. I'd go so far as to create enums that will only
       | have two or even just one option if expansion is expected soon
       | enough, with an optional state encoded in the type system as
       | well. If your database can't store optional/maybe values, I'd add
       | an enum variable.
       | 
       | Of course I'd make an exception if you're terribly resource
       | constrained. If you need to save every bit, document the hell out
       | of it and optimize your code any way you want.
        
       | aliswe wrote:
       | In C# I would definitely use the nullable "bool?" then you can
       | use null coalescing operator to default to either true or false:
       | "value ?? false"
        
       | throwaway81523 wrote:
       | In the code, it's considered something of a smell to use a bool
       | at all. Look up "Boolean blindness". Basically instead of a bool
       | field saying whether the person wants the deluxe upgrade, you'd
       | have an enum whose values are Regular | Deluxe. How you would
       | represent that in a db would depend on how your implementation
       | works.
        
       | sshine wrote:
       | Yes, I'd have an enum with Yes, No and Unset.
       | 
       | Many languages have a typed "no value" value that is composable
       | with other types: Maybe<T>, Option<T>, Nullable<T>.
       | 
       | In other situations I might have an Option<Yes, No>, i.e. when I
       | want a non-null value to indicate that it isn't Yes or No. The
       | reason why I don't want a null, in general, is that it's the
       | billion dollar mistake:
       | 
       | https://hackernoon.com/null-the-billion-dollar-mistake-8t5z3...
       | 
       | But if the user can actually choose "Unset", then that's a
       | choice, too.
       | 
       | "Unset" might, for example, have implications that other null-ish
       | answers don't have in the future.
       | 
       | As for SQL: I might go for a NULL for performance, knowing that
       | if another neither-yes-or-no option with different implications
       | came, I might have to migrate the NULLs.
        
         | nybble41 wrote:
         | When the final record should have either Yes or No, but the
         | there is an intermediate state where the answer could be
         | missing, you want something like a Maybe<Bool> which you can
         | map to a simple Bool as part of validation. Ideally you'd use a
         | functor-style parameter for this, for example in Haskell:
         | data Record f = Record { ..., someField [?] f Bool, ... }
         | type PartialRecord = Record Maybe       type CompleteRecord =
         | Record Identity            getCompletedRecord [?] PartialRecord
         | - Maybe CompleteRecord       getCompletedRecord (Record { ...,
         | someField, ... }) =         Record <$> ... <*> (Identity <$>
         | someField) <*> ...
         | 
         | This way you can define your record type once and handle all
         | the missing fields in a uniform way, and functions can easily
         | indicate whether they expect a partial record full of Maybes or
         | a complete record with no potentially missing fields. You can
         | use the same type definition for other things, too, by
         | substituting different functors in place of Maybe or Identity.
         | For example, `Record ToString` where `ToString a` is a newtype
         | over `(a - String)` could be a record of functions describing
         | how to render each field as a string.
         | 
         | In the DB you would need to store incomplete records in a
         | separate table, since they have different validation rules.
         | Queries against the main table should be able to assume that
         | all the records are complete.
        
       | jrockway wrote:
       | I don't have general advice, but you do have 3 states to
       | represent there; not acted upon by user, yes, and no. That's what
       | enums are.
       | 
       | I think "we'll pass in a pointer to the actual data and if it's
       | unset the pointer won't point to a valid memory location" is a
       | weird pattern that the industry should probably stop doing. So no
       | *bool type here.
       | 
       | Database nulls are maybe reasonable, but mean many things that an
       | explicit enum doesn't. "This column was added after the fact and
       | we didn't want to set a default value", "this column is
       | optional", etc. The more ambiguous the meaning, the more mistakes
       | that can be made.
       | 
       | (I think someone is going to say, "don't write the form to the
       | database until it's valid", but you have to have some way to save
       | work in progress between website visits or "my Internet died",
       | right? If you just dump a JSON blob in local storage, you still
       | have the same representation problem; the field has 3 possible
       | values, and you can't just pretend like it only has two because
       | there is a built-in type that has two possible values.)
        
       | j1elo wrote:
       | TypeScript is nice because its null type is explicit. So if a
       | type claims to be bool, _it is really bool_ (either true or
       | false, nothing else).
       | 
       | For a nullable variable of type T (including boolean), the type
       | must be explicit about it:                   let choice: T |
       | null;
       | 
       | Another nice alternative is an Option type, like Rust has. I
       | think it would be something like Option<bool>.
       | 
       | But those (a null, or a None, respectively) would be only choices
       | if internally, at the code level, the Unset case must be handled
       | as some kind of extraordinary scenario. If it was possible for
       | users to make a conscious decision to leave it unset, i.e. if
       | Unset is part of the valid choices offered by the user-facing
       | API, then I'd encode it as a proper possible state, in an enum.
        
         | gnabgib wrote:
         | And it's terrible because there's both `null` and `undefined`
         | which have largely the same meaning but aren't equal. Depending
         | on what you're interacting with, you may need to use one over
         | the other or coerce. Especially common with developers from
         | other languages who don't even know of the gotcha
        
       | NovemberWhiskey wrote:
       | Neither?
       | 
       | If the user responses are in a table of (field_id,
       | value_selected, ...) then you just use an outer join vs the
       | available field_id for the form and get back NULLs automatically
       | for the unpopulated selections.
       | 
       | That way if you (say) add new selections to forms that are
       | partially complete, reasonable things happen without any further
       | logic.
       | 
       | Why record data to say you don't have data?
        
       | nescioquid wrote:
       | The "ternary boolean" strikes me as a smell because it has to do
       | with how you initialize your code/state and enforce pre- and
       | post-conditions.
       | 
       | If you allow users to complete the form without choosing T/F on
       | your field, what happens? If you fail or prevent the submission,
       | then I would not write the schema to capture the value as a
       | nullable field. If you do accept the form without the user
       | selecting T/F, then you are defaulting to either T/F and the
       | field should likewise not be nullable.
       | 
       | If the value _is_ really nullable and you are using the presence
       | of null to decide anything, then it seems reasonable to enumerate
       | the state space explicitly with an enum so that the next dev
       | riding by on a horse doesn 't mistake your ternary boolean for a
       | simple null.
       | 
       | You can make null work in any of those cases, it just seems like
       | a headache and potential hazard, but could also just be personal
       | aesthetics on my part.
        
         | varispeed wrote:
         | You need a third state to know that user has not made a choice.
         | If you don't want to make a choice for the user, for instance,
         | you don't want to default to F or T, then third state tells
         | exactly that user has to make a choice.
        
           | dathinab wrote:
           | I would say using null as 3rd state is only ok iff it's part
           | of the domain logic and the 3rd state is literally no choice
           | was made, i.e. nothing, i.e. null.
        
           | nescioquid wrote:
           | Agreed. Sorry if I wasn't clear, but I consider that to fall
           | into the "value _is_ really nullable and you are using the
           | presence of null to decide anything... " scenario, in which
           | case, this is important to the domain, so explicitly model it
           | so the next dev doesn't trip over the special meaning of null
           | in this case.
           | 
           | Again, personal judgment and aesthetics, probably.
        
       | sprite wrote:
       | I would just use NULL
        
       | culpable_pickle wrote:
       | This is slightly different and a funny, but you reminded me of
       | https://thedailywtf.com/articles/What_Is_Truth_0x3f_
        
       | [deleted]
        
       ___________________________________________________________________
       (page generated 2022-06-17 23:02 UTC)