• by nescioquid on 6/17/2022, 8:30:28 PM

    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.

  • by jeroenhd on 6/17/2022, 8:58:32 PM

    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.

  • by SigmundA on 6/17/2022, 8:34:33 PM

    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.

  • by jrockway on 6/17/2022, 8:42:15 PM

    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.)

  • by DubiousPusher on 6/17/2022, 9:46:30 PM

    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.

  • by marginalia_nu on 6/17/2022, 9:13:43 PM

    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.

  • by disintegore on 6/17/2022, 8:38:45 PM

    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.

  • by IceMetalPunk on 6/17/2022, 7:41:10 PM

    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.

  • by sshine on 6/17/2022, 8:41:06 PM

    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.

  • by rvdginste on 6/17/2022, 9:46:24 PM

    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.

  • by j1elo on 6/17/2022, 9:07:29 PM

    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.

  • by culpable_pickle on 6/17/2022, 8:48:34 PM

    This is slightly different and a funny, but you reminded me of https://thedailywtf.com/articles/What_Is_Truth_0x3f_

  • by 8note on 6/18/2022, 2:18:41 AM

    Ive used UNSET in enums before, to describe things for a one-big-object state in workflows before the relevant step has been run.

    I don't think I've used it to describe true or false though. Possibly as a third option, but the names for the options end up well described, not just true and false.

    Looking back, I'd much prefer to make lots of types, describing the object along the way. In typescript it would be real easy to describe too, just &-ing the steps' results to be main object along the way

  • by adzm on 6/17/2022, 8:39:03 PM

    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.

  • by dathinab on 6/17/2022, 8:50:35 PM

    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).

  • by throwaway81523 on 6/17/2022, 10:11:46 PM

    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.

  • by NovemberWhiskey on 6/17/2022, 8:47:20 PM

    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?

  • by stevefan1999 on 6/18/2022, 3:00:24 AM

    The Yes No Unset enum seems intriguing to me -- in Rust term it is an Option<bool>. Usually this mean I have to unwrap_or but depending on the context it can be true or false

  • by aliswe on 6/17/2022, 8:59:04 PM

    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"

  • by dvh on 6/17/2022, 8:34:45 PM

    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...

  • by sprite on 6/17/2022, 8:30:29 PM

    I would just use NULL

  • by olliej on 6/17/2022, 8:40:15 PM

    You could be using Objective-C with NSBoolean, so you have @YES, @NO, and nil :D

  • by 0xfacfac on 6/18/2022, 12:44:26 AM

    In JS/TS, I use `undefined` for unset, then `true` and `false`.