Playing “What If” with “If”

Following the rabbit hole all the way down…

Jamis Buck
6 min readJun 18, 2016

One of my favorite programming games is playing “what if”. I’ve written about some of these experiments before, with “The Dynamic Def” and “Playing with Constants, Methods, and Superclasses” (among others), but here’s another example that led me down a rabbit hole and taught me a few new tricks.

To set this up, we need some Ruby grammar review. You probably already know this, but in Ruby, the if “statement” is actually an expression. For example, this is perfectly legal:

x = if something? then
:true_result
else
:false_result
end

Elementary, yes. Here’s another one, even more fundamental: the condition for an if statement is an expression, too. Right? Well then, if the condition is an expression, and an if statement itself is also an expression…

WHAT IF you put an if statement in the condition of another if statement?

if (
if (
if A then
B
else
C
end
) then
D
else
E
end
) then
F
else
G
end

It doesn’t take long to type that up, and sure enough, Ruby accepts it. It’s legal syntax. But what does it even mean? Does it make any sense at all? Is it even possible to come up with values for A, B, C, D, and E such that either F or G are the result?

Hmmm! Let’s think about this a bit.

The innermost if statement, the one that evaluates to either B or C, can be reduced to a sum of two products — two AND statements joined by an OR:

if A then
B
else
C
end
# is identical to:A && B || !A && C

So we can simplify that original if statement a bit by replacing the innermost if statement:

if (
if A && B || !A && C then
D
else
E
end
) then
F
else
G
end

Okay, then, we can use this same trick on the next nested if statement:

if A && B || !A && C then
D
else
E
end
# is identical to(A && B || !A && C) && D || !(A && B || !A && C) && E

Aaand now my brain starts hurting. I remember a few things from my predicate calculus classes in college (long, long ago), like De Morgan’s Law, which lets us expand the negated terms like so:

(A && B || !A && C) && D || !(A && B || !A && C) && E# becomes(A && B || !A && C) && D || !(A && B) && !(!A && C) && E# or, taking it one step more:(A && B || !A && C) && D || (!A || !B) && (A || !C) && E

But I’ll be honest, my predicate calculus skills are crazy rusty, and were never anything particularly worth bragging about.

So let’s try another angle. Let’s come up with some meaningful (if arbitrary) interpretations of those variables and see if we get something that makes sense.

How about this?

# A = is_hot_weather?
# B = eats_ice_cream?
# C = eats_apple_pie?
# D = feels_sick?
# E = catches_cold?
# F = rest_in_bed
# G = play_outside
if (
if (
if is_hot_weather?
eats_ice_cream?
else
eats_apple_pie?
end
) then
feels_sick?
else
catches_cold?
end
) then
rest_in_bed
else
play_outside
end

Hey, that almost makes sense! If we translate this into English, we might come up with the following series of statements:

  1. If the weather is hot, then I might eat ice cream. Otherwise, I might eat apple pie.
  2. If I eat either ice cream, or apple pie, I might feel sick. Otherwise, I might catch cold.
  3. If I feel sick, or if I catch cold, I will rest in bed. Otherwise, I will play outside.

That works! I think… My confidence falters a bit if I plug those conditions into that expanded boolean expression:

(A && B || !A && C) && D || (!A || !B) && (A || !C) && E# becomes…(is_hot_weather? && eats_ice_cream? || 
!is_hot_weather? && eats_apple_pie?) &&
feels_sick? ||
(!is_hot_weather? || !eats_ice_cream?) &&
(is_hot_weather? || !eats_apple_pie?) &&
catches_cold?

When that statement evaluates to true, the result is “rest in bed”. Otherwise, it is “play outside”. Once again, I can’t quite wrap my mind around it. Was my English translation accurate? Does it really represent what I came up with? How can I even know?

Well, short of trying my rusty, rusty hands at a mathematical proof, I might try building a truth table. This is just a table of all possible true/false combinations for all variables, along with the value of the expression when the variables have those values. For example, a simple A && B has the following truth table:

A  B  (A && B)
--------------
f f f
t f f
f t f
t t t

In other words, A && B is only true when both A and B are true. But our original expression has five variables: A, B, C, D, and E. Our truth table needs to include all permutations of true/false for those five variables. This means our table would need 2⁵ (32) rows…and I really don’t want to manually build that out.

Well, heck. I write software for a living. I can fix this. I can just write a truth table generator, and then feed it that five-variable monstrosity of an expression:

Expression:
(A && B || !A && C) && D || (!A || !B) && (A || !C) && E
A B C D E | =
---------------+---
f f f f f | f
t f f f f | f
f t f f f | f
t t f f f | f
f f t f f | f
t f t f f | f
f t t f f | f
t t t f f | f
f f f t f | f
t f f t f | f
f t f t f | f
t t f t f | t
f f t t f | t
t f t t f | f
f t t t f | t
t t t t f | t
f f f f t | t
t f f f t | t
f t f f t | t
t t f f t | f
f f t f t | f
t f t f t | t
f t t f t | f
t t t f t | f
f f f t t | t
t f f t t | t
f t f t t | t
t t f t t | t
f f t t t | t
t f t t t | t
f t t t t | t
t t t t t | t

Look at that! The truth table cannot lie; our expression can indeed evaluate to either true or false, depending on the inputs! That’s promising — it means that the expression isn’t meaningless. Let’s try one of those combinations:

# is_hot_weather? = true
# eats_ice_cream? = false
# eats_apple_pie? = false (unused)
# feels_sick? = true (unused)
# catches_cold? = true
# => true

That is to say (in the context of the original `if` statement), the weather is hot, and I do not eat any ice cream, but I catch cold. So the expression is true, which results in “rest in bed”.

Or this one:

# is_hot_weather? = true
# eats_ice_cream? = false
# eats_apple_pie? = true (unused)
# feels_sick? = true (unused)
# catches_cold? = false
# => false

This interprets to the weather being hot, but I don’t eat ice cream, nor do I catch cold. Thus, I go outside to play.

It’s no proof, but I’m feeling more confident about it now. Nesting if statements actually seems to work, logically!

Not that I’d ever (ever!) actually do that in real code, but it was a fun playground for experimentation. And it got me to write a truth table generator, and eventually even introduced me to Karnaugh maps!

There’s always more to learn, and play is a great way to discover it. Try it yourself — look at some code you’ve written recently and ask yourself: what if? You may be surprised at where it leads you…

--

--