< go back to the blog article list
trupples' blog
"To be, or not to be, that is the question"
Interpreting the famous Hamlet quote as a logic statement is a thing people have
been doing for ages both just because it's a fun thing to do and to

Seeing "2b or not 2b" on the gamedev discord server, I had the immediate
reaction of "yeah I already saw that so many times, don't really care", yet
there was a part of me who really wanted to screw around with this in
javasacript, because everyone
Expectations
The way English works, the quote should always evaluate to true if using the logical OR operator instead of the conjunction (part of speech, not the logical operator) presenting a contrasting choice, just like the "joke" does. If X is a binary value, X OR the inverse of itself will always be true.
The first attempt - naively taking it literally
tbontb = (b ) => 2 *b || !2 *b
console .log(tbontb (0 ), tbontb (1 ), // 0 2
tbontb ([] ), tbontb ({} ), // 0 NaN
tbontb ("" ), tbontb ("1" ), // 0 2
tbontb ("0" ),tbontb (true ), // 0 2
tbontb (false )) // 0
Oh boy, many falsey results. Natural language doesn't agree with Javascript (and
pretty much every programming language with a decent operator precedence) on
this. This is because "to be" is a single 2*b
isn't. Nothing fishy should be happening on the
left side of the ||
. The unexpected behaviour is due to the
!
operator being applied to 2
before the
multiplication can take place. This way if b
is truthy, castable to
a number and that number is also truthy (i'm looking at you, []
)
the left side of the ||
will be truthy so the engine won't even bother
evaluating the right side and the function will return the double of
b
. If b
or it casted to a number is falsey, the left
side of the operation will be false so the function will return whatever the
right side is. The 2
will always be negated, resulting in
false
, which will be multiplied with b
. Anything
multiplied with false
gives a falsey result in Javascript (numbers
just cast false == 0
, false*{}
is NaN
for some reason, etc.)
Fixing precedence - Can I still break it?
tbontb = (b ) => 2 *b || !(2 *b )
console .log(tbontb (0 ), tbontb (1 ), // true 2
tbontb ([] ), tbontb ({} ), // true true
tbontb ("" ), tbontb ("1" ), // true 2
tbontb ("0" ),tbontb (true ), // true 2
tbontb (false )) // true
Now all the falsey results of the previous implementation have turned into
true
. Beside the values not being all booleans, the function seems
to work just like the English sentence: no more falseyness. Of course, there
still is a way to trick it, but I'm pretty sure this is cheating:
function Cheater () {
this .value = 0 ;
}
Cheater .prototype.valueOf = function () {
return this .value++;
}
let myCheater = new Cheater ();
console .log(tbontb (myCheater )); // false
This works because when the tbontb
function first gets the value of
b
, the cheater returns 0
so that the left side is
falsey. When evaluating the right side, the function is being given the "new"
value b = 1
, thus making the right side also falsey.
Another thing I stumbled upon when making the cheater is that
Cheater.prototype.valueOf
(or any other prototype function) must be
declared with function() { ... }
. () => { ... }
doesn't work:
function FailedCheater () {
this .value = 0 ;
}
FailedCheater .prototype.valueOf = () => this .value++;
let yourCheater = new FailedCheater ();
console .log(tbontb (yourCheater )); // true
This confused me at first, but it appears that
this
doesn't work the
same in arrow functions.
Bulletproofing it
Okay, now let's try to make this invincible. An idea would be storing the value of b and using that so it doesn't change. Also, making the function always return bools would get us closer to the "logical" interpretation from which we started.
tbontb = (b ) => {
let B = b.valueOf && b.valueOf() || b ;
return !!(2 *B || !(2 *B ));
}
let myOtherCheater = new Cheater();
let yourOtherCheater = new FailedCheater();
console .log(tbontb (myOtherCheater )); // true
console .log(tbontb (yourOtherCheater )); // true
The and on the let B = ...
line checks if b
has a
valueOf
function. If it has, it sets B
to that
function's return value. Else, it falls back to just using the value of
b
.