Trilean¶
What Is It?¶
A trilean is an atomic data type with three distinct states: “true”, “maybe”, and “false”. It is designed to behave like a boolean, but with the additional feature of “uncertainty.”
In order to understand trilean, one must remember that “true” and “false” are certain states, entirely distinct from and unequivalent to “maybe”. All of trilean’s behavior is based around this rule.
Why Use Trilean?¶
The logic of trilean has historically been achieved in C++ using either an enumeration or a pair of booleans. However, trilean offers a couple of distinct advantages:
A trilean variable is exactly one byte in size.
All three states can be represented in a single variable.
Trilean is fully compatible with boolean and its constants.
Conditional logic with trilean is simpler and cleaner.
Using Trilean¶
Including Trilean¶
To include Trilean, use the following:
#include "pawlib/core_types.hpp"
“Maybe” Contant¶
Along with the normal true
and false
constants provided by C++,
trilean offers a maybe
constant. This new constant is designed to
be distinct from true
and false
, yet usable in the same manner
for trileans.
Defining and Assigning¶
Defining a new trilean variable is simple.
tril foo = true;
tril bar = maybe
tril baz = false;
In addition to the three constants, you can assign other booleans and trileans.
bool foo = true;
tril bar = foo;
tril baz = bar;
// foo, bar, and baz are all 'true'
Conditionals¶
Obviously, one can compare a trilean against of its state constants directly.
tril foo = maybe;
if(foo == true)
// code if TRUE...
else if(foo == maybe)
// code if MAYBE...
else if(foo == false)
// code if FALSE...
However, one can also test a trilean in the same manner as a boolean.
tril foo = maybe;
if(foo)
// code if TRUE...
else if(~foo)
// code if MAYBE...
else if(!foo)
// code if FALSE...
You will notice that, in addition to the familiar tests for “true”
(if(foo)
) and “false” (if(!foo)
), trilean has a third unary
operator, ~
, for “maybe” (if(~foo)
).
Important
Remember, neither the “true” or “false” conditions will ever match “maybe”.
This basic behavior is what makes trilean so useful. For example, you may want
to repeat a block of code until you encounter specific scenarios to cause it
to either pass or fail, using something like while(~foo)
.
Comparisons¶
Trilean can be compared to booleans and other trileans using the ==
and
!=
operators.
tril foo = true;
tril bar = maybe;
bool baz = true;
if(foo == bar)
// This fails.
if(foo != bar)
// This passes.
if(foo == baz)
// This passes.
if(baz == foo)
// This passes.
if(baz == bar)
// This fails.
What About Switch?¶
The idea of allowing a trilean to cast to an integer was discussed and debated in great deal. Finally, the decision was made to prevent casting a trilean to anything but a boolean (discussed later).
This means that trileans are not compatible with switch statements. While this may be initially disappointing to anyone used to using an enumeration for three-state logic, one will notice that an if-statement covering all three states of a trilean has at least 4 less lines of boilerplate.
tril foo;
/* This code demonstrates an if statement covering all three states
* of a trilean. */
if(foo)
{
// Some code.
}
else if(~foo)
{
// Some code.
}
else if(!foo)
{
// Some code.
}
Certainty¶
Because trilean stores its data in two bits, it is possible for a variable to track its last certain state. In other words, if a trilean is “true” or “false,” and then is set to “maybe”, that true/false value is still being stored behind the scenes.
To make this useful, trilean offers a certain()
function, which returns
the last certain state of the variable without actually modifying itself.
tril foo = true;
foo = maybe;
bool bar = foo.certain();
// bar is now 'true', while foo is still 'maybe'
This behavior can also be used to revert a trilean to its last certain state.
tril foo = true;
foo = maybe;
foo = foo.certain();
// foo is now 'true'
Implications¶
The concept of “certainty” technically allows one to recognize and use four trilean states:
Certain true (
if( foo )
)Uncertain true (
if( ~foo && foo.certain() )
)Uncertain false (
if (~foo && !foo.certain() )
)Certain false (
if (!foo)
)
Uncertainty Variables¶
The “magical” behavior of assigning the constant “maybe” not affecting the previous certain state is achieved through “uncertainty” variables. Any time an uncertainty is assigned to a trilean, only the uncertainty of the trilean is affected.
The constant “maybe” is usually the only uncertainty object you will interact
with. However, it is possible to create your own certainty
. Be aware that
this data type does not provide any mechanism for modifying it after creation.
uncertainty my_maybe(true);
uncertainty my_certain(false);
As is expected, an uncertainty can never match “true” or “false”, or be
directly cast to a boolean. However, the ~
operator works as with trileans.
if(~my_maybe)
// This passes.
if(~my_certain)
// This fails.
if(~my_certain == false)
// This passes.
The usefulness of an uncertainty variable is, quite probably, limited to allowing manipulation of a trilean’s certainty.
Gotchas¶
Casting to Bool¶
In order to preserve the core logic that “maybe != true” in statements like
if(foo)
, casting a trilean to a boolean causes “maybe” to be converted to
“false”.
tril foo = maybe;
bool bar = foo;
// bar is now 'false'
In most cases, it is recommended to use the certain()
function.
Note
In case you were wondering, we ensured that “maybe != false” in comparisons
and conditionals by separately overloading the !
, !=
, and ==
operators.