Graphic by Keith Ohlfs |
|
When writing methods that return booleans, many people make their code much more complicated than it needs to be. For instance, suppose you want a method that detects if a buggle has a wall both to its left and its right. One way of doing this is as follows:
public boolean hasWallsOnBothSides () { if (isWallToLeft()) { if (isWallToRight()) { return true; } else { return false; } } else { return false; } }
However, this is much more verbose than it needs to be. First of all, study the inner conditional:
if (isWallToRight()) { return true; } else { return false; }
If isWallToRight() returns true, then the conditional returns true; if isWallToRight() returns false, then the conditional returns false. So the above statement is equivalent to the single line.
return isWallToRight();
It is alway possible to replace the pattern if (E) {return true;} else {return false;} by the pattern return E; for any boolean expression E. We expect you to make this simplification in your code. Similarly, it is always possible to replace the pattern if (E) {return false;} else {return true;} by the pattern return !E;.
Now our method looks like
public boolean hasWallsOnBothSides () { if (isWallToLeft()) { return isWallToRight() } else { return false; } }
It turns out that this can be simplified further to
public boolean hasWallsOnBothSides () { return isWallToLeft() && isWallToRight(); }
This makes sense in terms of the contract for hasWallsOnBothSides() --- it should return true if and only if there are walls to both the left and the right. But how do we know when it's legal to make such a simplification?
Just as it is possible to simplify algebraic expressions by using rules like cancellation, commutativity, associativity, distributivity, etc., it is possible to simplify boolean expressions and conditional statements by using rules. Here we present the rules that you need to know.
In the following table, the patterns of boolean expressions in the left column can always be replaced by the ones in the right column. The notation boolExp stands for any expression that evaluates to a boolean.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
You should convince yourself that all of the above simplifications are logically valid (i.e., substituting both true and false for boolExp makes the two columns have the same value). Additionally, you should convince yourself that the simplifications preserve errors and nontermination (i.e., if boolExp throws an exception or gets stuck in an infinite loop, both columns still have the same behavior.) In some cases, the "short-circuit" nature of && and || is critical for satisfying the second criterion. This second criterion is more subtle than logical validity, and fails for the following two logically valid simplifications; for this reason, they are not included in the above table:
|
|
|
|
Note that each of the simplifications in the larger table makes the expression shorter. So if you continue to apply the simplifications, you will eventually reach a point where you can no longer apply any more simplifications. Furthermore, it turns out that no matter what order you apply the simplifications in, you will eventually reach a unique shortest expression.
There are other important valid transformations that preserve meaning but destroy the property of reaching a unique simplest result. Here are some of these; the first two are known as DeMorgan's laws:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
You should verify that these simplifications are logically valid and preserve error and non-termination semantics. To see that the first rule destroys the property of reaching a unique shortest expression, consider the expression
Using the simplification that eliminates double negation, this can be simplifed in one step to x || (! y). But it can also be simplified by the first DeMorgan simplification to ! ((! x) && y). Both of these expressions cannot be simplified any further. There are similar examples involving the other rules.
Some of the above patterns may seem silly (you're probably saying to yourself, "I would never write that!"). But even though you may not write them yourself, they may show up in the results of some of the simplifications of conditionals shown below. In this context you would be expected to simplify them.
Now consider the following single transformation on conditional statements that return boolean results:
|
|
You should convince yourself that this rule is logically valid and preserves error and non-termination semantics. However, it does not simplify the statement. Indeed, the result requires two evaluations of boolExp1 where the initial expression only required one! However, if at least one of boolExp2 or boolExp3 is true or false, then the boolean expression simplifications from above can lead to a simpler result. (Obviously, similar results hold if boolExp1 is true or false.) Below are some important instantiations of this idea.
|
|
|
|
|
|
|
|
|
|
|
|
|
|