Booleans are people too! Well, George Boole was, anyway, and he is the one credited with and named for the mathematical operations performed on variables defined to carry one of only two states: true or false. Or on and off. Or zero and not-zero. Call those two states what you will. And always capitalize the adjective Boolean.
Some programming languages, such as Pascal, have a distinct Boolean type; there, it is defined as the enumeration (false, true), wherein, if you take the ordinal values of the enumerations, false maps to zero and true maps to 1.
LSL has no such Boolean type, as it follows the lead of the C-family languages in this regard. Here, a Boolean value is simply an interpretation of an expression, dependent on both its type and its value.
Let’s start with the seemingly obvious example, integers.
An integer value of zero can be interpreted to be false; to use current jargon, zero is a “falsy” value. Any non-zero integer can be interpreted to be true; -286, for instance, is a “truthy” value.
Try out this and all subsequent examples to confirm what you think should be the output:
[lsl]
default {
state_entry() {
if (0) llOwnerSay ("0 is truthy");
if (1) llOwnerSay ("1 is truthy");
if (286790) llOwnerSay ("286790 is truthy");
if (-1) llOwnerSay ("-1 is truthy");
}
}
[/lsl]
Complication #1: LSL defines two integer constants: FALSE and TRUE (letter-case is significant!). FALSE == 0, and TRUE == 1 (not any non-zero value!). This can lead to confusion in looking for values that are truthy:
[lsl]
default {
state_entry() {
if (0 == TRUE) llOwnerSay ("0 == TRUE is truthy");
if (1 == TRUE) llOwnerSay ("1 == TRUE is truthy");
if (286790 == TRUE) llOwnerSay ("286790 == TRUE is truthy");
if (-1 == TRUE) llOwnerSay ("-1 == TRUE is truthy");
}
}
[/lsl]
For example, -1 is indeed a value that can be interpreted to be true. But it’s not equal to the defined constant TRUE. -1 is a truthy value, but -1 != TRUE. On the other hand, 0 is a falsy value, and as well, 0 == FALSE. No, really.
Complication #2: comparison expressions using the so-called logical operators yield integer results. For integer operands, you’ll get 0 or 1, but don’t count on this with other types. For those, rely instead on getting zero or not-zero:
[lsl]
default {
state_entry() {
integer is_2_equal_to_77 = (2 == 77) ;
llOwnerSay ((string) is_2_equal_to_77) ;
integer is_list_nonEmpty = (["a", 42, <1,1,1>] != []) ;
llOwnerSay ((string) is_list_nonEmpty) ;
}
}
[/lsl]
Complication #3: types other than integers can have values that are truthy or falsy. The empty string (“”)—that is, a string with a character length of zero—is falsy; all other strings are truthy. the string “0” is truthy in LSL, though not necessarily so in other languages.
Keys are strings with a specified syntax and character length. Any string can be cast to a key type, but how it is interpreted is another matter. The NULL_KEY is supposed to be falsy and all other keys truthy. But watch out: keys do not yield integer values in conditional expressions, so you cannot use logical operators outside of == or !=. That means the operators || and && and ! cannot be used directly with keys.
[lsl]
// The NULL_KEY is "00000000-0000-0000-0000-000000000000" and is a string unless cast appropriately.
key keyCheck = NULL_KEY;
default {
state_entry() {
if (NULL_KEY) llOwnerSay ("This says a bare NULL_KEY is truthy.") ;
if ((key) NULL_KEY) llOwnerSay ("This says a NULL_KEY, cast as a key, is truthy.") ;
if (keyCheck) llOwnerSay ("This says NULL_KEY in a key variable is truthy.") ;
if ((string) NULL_KEY) llOwnerSay ("This says NULL_KEY, as a string, is truthy.") ;
}
}
[/lsl]
The ZERO_VECTOR (<0,0,0>) is falsy, all others truthy.
The ZERO_ROTATION (<0,0,0,1>) is falsy, all others truthy.
The empty list ([]) is falsy, all others truthy.
The float 0.0 is falsy, but I exhort you never to use floating-point numbers as Boolean values due to round-off errors. For this reason, in particular, never test a float for strict equality; always consider a tolerance range and test for inequalities.
// =============================================================
Now, I went through all that so I could talk about this.
A fundamental application of Boolean values is the on/off switch, which includes the mechanism by which on becomes off and vice-versa. If we use the defined constants, our code looks much like this:
[lsl]
integer g_switch = FALSE;
default {
touch_start (integer numDetected) {
if (g_switch) {
g_switch = FALSE;
// Turn something off here.
}
else {
g_switch = TRUE;
// Turn something on here.
}
}
}
[/lsl]
Since logical operations upon integer values yield TRUE or FALSE, we can take a small shortcut and flick the switch in one step:
[lsl]
integer g_switch = FALSE;
default {
touch_start (integer numDetected) {
// On becomes off and the other way around.
g_switch = ! g_switch;
if (g_switch) {
// Turn something on here.
}
else {
// Turn something off here.
}
}
}
[/lsl]
If, however, we elect to broaden our interpretation of true vs false by using truthy vs falsy values, then we can use arithmetic and/or bitwise operations:
[lsl]
integer g_switch = 0;
default {
touch_start (integer numDetected) {
// Granted this is overkill, but it is illustrative.
g_switch = (g_switch + 1) & 1 ;
if (g_switch) {
// Turn something on here.
}
else {
// Turn something off here.
}
}
}
[/lsl]
And if you’re building a three-way switch, then you cannot effectively use the two-way values TRUE and FALSE, since arithmetic is not defined, at least not philosophically, for Booleans:
[lsl]
integer g_switch = 0;
default {
touch_start (integer numDetected) {
g_switch = (g_switch + 1) % 3 ;
if (g_switch == 0) {
// Turn something off here.
}
else if (g_switch == 1) {
// Turn something on at the lower level.
}
else { // g_switch must == 2
// Turn something on at the higher level.
}
}
}
[/lsl]
My advice is never to mix these two treatments. If you begin using the constants TRUE and FALSE, then continue to assign these values to your Boolean variable as needed, or use the logical operators with integer operands to set the switch.
But if you use arithmetic or bitwise expressions to set the switch, then test for zero or other non-zero values explicitly, for you risk that the switch may not equal TRUE, even though it may be truthy.
Update
Oy. Never say never. I found an elegant exception to my advice above, which fills an expressive need, yet relies upon mapping the result of a logical expression to an integer value. This mapping in turn relies upon definitions which cannot change upon pain of backwards-incompatibility, so I think I’m safe.