11

Consider this function, which you can think of as a truth table:

public Foo doSomething(bool a, bool b) {

       if ( a &&  b) return doAB();
  else if ( a && !b) return doA();
  else if (!a &&  b) return doB();
  else if (!a && !b) return doNotANotB();

  else throw new Exception("Well done, you defeated boolean logic!");
}

The compiler insists on that last else clause. But from a truth table's perspective, that is an impossible state.

Yes, it works, and yes, I can live with it. But I'm wondering if there is some mechanism in c# to avoid this sort of code, or if I've missed something obvious?

UPDATE:
For bonus points, and purely out of curiosity, are there any languages that deal with this sort of thing differently? Maybe it's not a language matter, but rather one of a smart compiler (but the edge cases would be unimaginably complicated I suppose).

Bobby B
  • 2,287
  • 2
  • 24
  • 47
  • 6
    Yes, there is such mechanism: simply drop the condition of the last `if`. – Sergey Kalinichenko Nov 11 '12 at 12:14
  • Very true! Pun intended. But if possible I would like the code to be "readable" or "expressive". Keep in mind this is a simple example. And in 7 months I'l be scratching my head wondering what I was thinking when I wrote it (thus the preferance for expressive code). – Bobby B Nov 11 '12 at 12:16
  • If the actions are not related to eachother, I would not use the `if/else if` pattern, but instead go for four separate `if` blocks followed throwing the exception to satisfy the compiler. In most cases though, there is a 'default' action that fits perfectly in an empty `else`. – C.Evenhuis Nov 11 '12 at 12:22
  • 1
    @C.Evenhuis: compiler will want an else after four separate if statements as well... – Bobby B Nov 11 '12 at 12:27
  • 2
    As far as your second question goes, there is at least one language that deals with this differently: it's C. The compiler simply does not care: the standard says that if you're wrong about your truth table and reaching the end with a return does occur, it's undefined behavior (i.e. a junk value is returned, your program crashes, or both). – Sergey Kalinichenko Nov 11 '12 at 12:31
  • Also, it is possible for another thread to change one of those variables while you are in the middle of the if chain. That's impossible for the compiler to know, so it's left to you. – kenny Nov 11 '12 at 13:26
  • @BobbyB exactly that's what I meant with "exception to satisfy the compiler". It should have read "...followed _by_ throwing..." though. – C.Evenhuis Nov 11 '12 at 21:55

4 Answers4

18

Considering the truth table, the last condition is entirely superfluos. It can be dropped without altering the logic of your program, like this:

public MyType doSomething(bool a, bool b) {

      if ( a &&  b) return doAB();
else  if ( a && !b) return doA();
else  if (!a &&  b) return doB();
else/*if (!a && !b)*/ return doNotANotB();
}

Now you have a final catch-all if, and your compiler is happy. You don't have to remove the condition altogether - I often find it a good idea to keep it around in a comment for readability.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 3
    I actually like leaving the commented-out code because it documents what the condition for last branch effectively is. +1 – usr Nov 11 '12 at 12:17
  • Yes as I noted in the comment above, this is true. But is there a way where I can express intent without resorting to implicit conditions? Probably not... – Bobby B Nov 11 '12 at 12:18
  • @BobbyB Absolutely, do not rush! There is a chance that someone who knows C# compiler from the inside sees this question, and shares an insightful answers with us. (I mean specifically [Eric Lippert](http://stackoverflow.com/users/88656/eric-lippert), though unfortunately he has not been answering too many questions lately). – Sergey Kalinichenko Nov 11 '12 at 12:26
  • 1
    I don't like this because it is easy to get the comment and the behaviour actually different . Say you missed two conditions from the truth table. Your else will catch both of them but your comment only notes a single one. A compiler that can detect exhaustive pattern matching such as f# is better if you have the choice. – bradgonesurfing Nov 11 '12 at 16:42
  • @bradgonesurfing I definitely agree with you about the compiler being better than humans at processing logical conditions. However, the question says that the three other conditions are exhaustive, so I wrote my answer with this assumption in mind. – Sergey Kalinichenko Nov 11 '12 at 22:10
  • @dasblinkenlight I tend in these situations to make the test specific and add an **else throw new Exception("Programming Error")** at the end. If you hit the exception via testing you know you have missed some condition in your truth table. If you use the else the error can propagate further into your system. – bradgonesurfing Nov 12 '12 at 06:12
7
if(a) return b ? doAB() : doA();
else return b ? doB() : doNotAnotB();

Or shorter:

return a ? (b ? doAB() : doA())
         : (b ? doB() : doNotAnotB());
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
6

Try f#. If it can detect exhaustive condition matching with its match directive then it doesn't require an else.

http://ganesansenthilvel.blogspot.co.at/2011/12/f-pattern-matching.html?m=1#!

> let testAND x y =
match x, y with
| true, true -> true
| true, false -> false
| false, true -> false
| false, false -> true

> testAND true false;;
val it: bool = true

and for an incomplete specification

> let testAND x y =
match x, y with
| true, true -> true
// Commented | true, false -> false 
| false, true -> false
| false, false -> true
> testAND true false;;

the compiler will say

Microsoft.Fsharp.Core.MatchFailureExcption: The match cases were incomplete at:....
Stopped due to error
bradgonesurfing
  • 30,949
  • 17
  • 114
  • 217
2
 public MyType doSomething(bool a, bool b)
        {
            switch(a)
            {
                case true:
                    if (b) return doAB();
                    return doA();
                default:
                    if (b) return doB();
                    return doNotANotB();

            }

        }

Update:

Note that your original statement is actually:

  public MyType doSomething(bool a, bool b)
        {
            if (a && b) return doAB();
            if (a) return doA();
            if (b) return doB();
            return doNotANotB();
        }

For fun and succintnes (if not readability :p):

static MyType doSomething(bool a, bool b)
        {
            return a && b ? doAB() : a ? doA() : b ? doB() : doNotANotB();
        }
Anthill
  • 1,219
  • 10
  • 20
  • I think I prefer the latter example. I don't think it is less readable as long as proper variable and method names are used, and perhaps some newlines and indentation. – Dave Cousineau Nov 11 '12 at 13:19
  • *Nods*, I like the latter myself although I think it's easy to trip up on the nested ternary evaluations. – Anthill Nov 11 '12 at 13:27
  • 1
    I needed to read that ternary a few times till I understood what went where. I prefer the second example which effectively reduces the original boolean states, so it makes it easier to understand. – Bobby B Nov 11 '12 at 13:31