How can I avoid an impossible boolean state in c#?
Asked Answered
J

4

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).

Janiuszck answered 11/11, 2012 at 12:11 Comment(7)
Yes, there is such mechanism: simply drop the condition of the last if.Adalia
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).Janiuszck
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.Satyr
@C.Evenhuis: compiler will want an else after four separate if statements as well...Janiuszck
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).Adalia
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.Nonreturnable
@BobbyB exactly that's what I meant with "exception to satisfy the compiler". It should have read "...followed by throwing..." though.Satyr
A
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.

Adalia answered 11/11, 2012 at 12:16 Comment(6)
I actually like leaving the commented-out code because it documents what the condition for last branch effectively is. +1Repeater
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...Janiuszck
@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, though unfortunately he has not been answering too many questions lately).Adalia
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.Reld
@Reld 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.Adalia
@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.Reld
C
7
if(a) return b ? doAB() : doA();
else return b ? doB() : doNotAnotB();

Or shorter:

return a ? (b ? doAB() : doA())
         : (b ? doB() : doNotAnotB());
Canasta answered 11/11, 2012 at 15:32 Comment(0)
R
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
Reld answered 11/11, 2012 at 13:8 Comment(0)
I
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();
        }
Inconsiderable answered 11/11, 2012 at 12:37 Comment(3)
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.Hybridism
Nods, I like the latter myself although I think it's easy to trip up on the nested ternary evaluations.Inconsiderable
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.Janiuszck

© 2022 - 2024 — McMap. All rights reserved.