< go back to the main page
< 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 sound clever, creating such atrocities as this ever so artifacty image

B=B=NOT(OR(B, B))

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 loves the way Javascript does logic.

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 entity in the former, but 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.