More On The Death of If-Else

Sunday, December 6, 2009

Aaron Feng posted recently on “The death of if-else, if, and else”. In the post Aaron rewrote some JavaScript conditional checks using a dispatch table type approach.

Following along with Aaron’s post using C#, we’d start with a list of Channel objects:

var channels = new List<Channel>
{
    new Channel { Number = 2,
                  Station = "NBC",
                  ShowTitle = "Saturday Night Live",
                  Genre = "comedy",
                  Repeat = true },
    new Channel { Number = 3,
                  Station = "ESPN",
                  ShowTitle = "College Football",
                  Genre = "football",
                  Repeat = false}
    // ...
};

Then we have the logic for deciding which channels to record:

public void Surf(IEnumerable<Channel> channels)
{
    foreach (var channel in channels)
    {
        if (channel.Genre == "football")
        {
            Record(channel);
        }
        else if (channel.Genre == "comedy" && 
                 !channel.Repeat)
        {
            Record(channel);
        }
        else if (channel.Genre == "crime" &&
                channel.ShowTitle != "Cops!")
        {
            Record(channel);
        }
    }
}

Rewriting the code using Aaron’s final approach would look like the following:

public void Surf2(IEnumerable<Channel> channels)
{
    var dispatch = new Dictionary<string, Action<Channel>>
    {
        { "football", c => Record(c) },
        { "comedy",   c => {if(!c.Repeat) Record(c);}},
        { "crime",    c => {if(c.ShowTitle != "Cops!") Record(c);}}
    };

    foreach (var channel in channels)
    {
        dispatch[channel.Genre](channel);
    }
}

Personally, I feel Aaron’s dispatch table is not a big improvement over the previous “if else” version. The actions in the dispatch table are too busy. I think a cleaner approach is to just extract the rules into a data structure – essentially build a collections of predicates to evaluate. Given a channel, the data structure can tell me if the channel should be recorded.

public void Surf3(IEnumerable<Channel> channels)
{
    var recordingRules = new Func<Channel, bool>[]
    {
        c => c.Genre == "football",
        c => c.Genre == "comedy" && !c.Repeat,
        c => c.Genre == "crime" && c.ShowTitle != "Cops!"
    };

    foreach (var channel in channels)
    {
        if(recordingRules.Any(rule => rule(channel) == true))
        {
            Record(channel);
        }
    }
}

Not quite as flexible, but for this specific example its easier to read and maintain.

What do you think? Is that better or worse?


Comments
gravatar Matt Hamilton Sunday, December 6, 2009
The function-list approach is neat, and you can still eliminate the "if":

var channelsToRecord = channels
.Where(c => recordingRules.Any(...));
foreach (var channel in channelsToRecord) ...
gravatar Aaron Feng Sunday, December 6, 2009
The reason I left the record function in the dispatch table is so that I can do something else other than record if I want to. For example, while surfing the channels, it could "recommend" a channel to my friends. This can be done only by changing the dispatch table. I noticed your dispatch table is hardcoded inside the class. It will be much more powerful if the table is passed in or defined globally. Therefore, making it possible to inject completely new behavior possible (like recommend instead of surf). Or the dispatch table can live outside of the project and created via Activator during run-time. Now the app can be configured to do something completely different without recompiling the core code.

Since C# isn't dynamic, Action<Channel> is not exactly the same as my JavaScript example. Depending on the dispatch table, the anonymous function can actually return something, therefore, making more like Func<T...>.
Amir Katz Monday, December 7, 2009
Hi,
First of all I really like your blog!

About this example, it looks like in this scenario there is no need for if else since all the "then" is the same. we always record.
Therefore your example can also solve this issue.
But if there will be a diffrent action for each if then your example wont be good enough. But Aaron can modify the table to solve it.
Philipp Monday, December 7, 2009
Isn't that a logical "OR" in the first place rather than an if-else scenario? All your "ifs" are doing the same thing, so i actually don't see an else:

if (condition1 || condition2 || condition3)
{
Record(channel);
}
gravatar Tom Janssens Monday, December 7, 2009
While I think the original article was obviously an example of exchanging a screwdriver with a knife for digging a hole, it still wonders me nobody sees it.

The example the original author was showing was just an example of badly implemented code, and I personally think he just replaced it with another piece of bad code.
Routing tables have it's use but not for the example he gave imho.

So here is my 0.02€ attempt (and yes, it is using an IF-statement):
<pre>
foreach (var c in channels)
if ( (c.Genre == "football")
|| (c.Genre == "comedy" && !c.Repeat)
|| (c.Genre == "crime" && c.ShowTitle != "Cops!"))
c.record();
</pre>

Simple, solid and easy on the eyes
(if text pre-formatting works here)

Kind regards,

Tom.
gravatar Paul Cowan Monday, December 7, 2009
The filter is the best approach here.

Lamda abuse is now a recognised (ALT?).NET social disease.

Lamdas seem to be encouraged everywhere.

The only reason this might appear to somebody to be better is becuase it is different and new.

If it is different and new then it is more exciting to right or at least less boring than writing an else if.

I don't think I understand he point of this.

What are we achieving by writing an arguably more complex construct for something simple?
gravatar Milan Negovan Monday, December 7, 2009
Scott, how about expressing that logic with several specifications instead? (http://en.wikipedia.org/wiki/Specification_pattern)
gravatar John Sonmez Monday, December 7, 2009
I definitely like Surf3 better. It is more clear. I really like the simplification of the logic here. If else to map is a great refactor pattern.
Mikhail Opletayev Monday, December 7, 2009
Depends on the meaning of "better or worse".

From the readability perspective a single "if" with 3 "or"-ed conditions would probably be the best.

From the performance perspective it really depends on the number of the rules.

For 2-3 rules a single if might be enough as there is no justification for an indirect call you incur with lambdas.

10-20 rules might work better with Surf3().

If you have a lot of rules Dictionary search is likely to be your best bet.
Amir Katz Monday, December 7, 2009
How about this one, for more Generic Actions:

public void Surf(IEnumerable<Channel> channels)
{
var dispatch = new Dictionary<Func<Channel, bool>, Action<Channel>>
{
{ c => c.Genre == "football", c => Record(c) },
{ c => c.Genre == "comedy" && !c.Repeat, c => Nofify(c)},
{ c => c.Genre == "crime" && c.ShowTitle != "Cops!", c => Record(c)}
};

foreach (var channel in channels)
{
KeyValuePair<Func<Channel, bool>, Action<Channel>>? pair = dispatch.FirstOrDefault(rule => rule.Key(channel));
if (pair.HasValue)
{
pair.Value.Value(channel);
}
}
}
gravatar Milan Negovan Monday, December 7, 2009
Just to add my 2c again. I believe lambda functions in .NET don't live up to their purpose when compared to what lambdas are for in a truly functional language, e.g. Haskell. Therefore using lambdas with dictionaries of Funcs,Actions, etc, is misguided to begin with.

IMO, it's better to express that logic in a different way, with a different pattern, as opposed to trying to come up with more ways to twist lambdas.
gravatar Eber Irigoyen Monday, December 7, 2009
I have personally been using the dictionaries of delegates for a long time, I like it much better than your code
gravatar zvolkov Monday, December 7, 2009
While the benefits of functional approach applicability to this case are arguable, I found many scenarios when functional approach helps separate the concerns and make the logic more readable.
gravatar James Ottaway Monday, December 7, 2009
I certainly prefer the Surf3 approach of collating the rules together then applying them to the channel collection.

As I read through Surf2 I was thinking of a very similar approach to what I was about to scroll down to in Surf3.
gravatar Bob Grommes Wednesday, December 9, 2009
This all seems like a next door neighbor I used to have to pretended not to swear by having a euphemism for every cuss phrase.

You have to make decisions and branch ... it's the whole point of software. You can dress it up, hide it behind stuff, but it's still happening. Why not simply be straightforward about it? It's not a shameful secret, after all. We're making decisions ... who knew.
gravatar Scott Allen Wednesday, December 9, 2009
@Bob:

I don't agree that making decisions or evaluating logic should force me to change the control flow of a program. The two don't have to be tied together.
gravatar Peter Kellner Thursday, December 10, 2009
My opinion is what ever feels good. I can follow all of them, and what about the case statement? My first reaction is the old "whenever we get a new hammer, everything looks like a nail", but on further thought, what the hec. It's very inventive and I do love Lambda expressions.
Josh Friday, December 11, 2009
public void Surf4(IEnumerable<Channel> channels)
{
foreach (var channel in channels)
{

switch(channel.Genre) {
case "football" :
Record(channel);
break;
case "comedy" :
if(!channel.Repeat) Record(channel);
break;
case "crime" :
if(show.channel.ShowTitle != "Cops!") Record(channel);
break;
}
}
}
Josh Friday, December 11, 2009
I'm sorry, I do think that Funcs are awesome and opens up many different ways of getting things done, the classic SWITCH statement is still cleaner and should execute faster (see above.)
gravatar Andrew Wilson Wednesday, January 20, 2010
I think the word missing here is "potential". By using if/then statements in my code, I am committed to them pretty literally until I change the code and compile. However, if I use either the funcs or dispatch approach I now have the added potential to make changes at runtime such as:

delete the whole route and replace it with something else.

add to the route to make it do more stuff.

I think that long term, that type of flexibility lets the application grow a little bit easier.

-A
Ivo s Wednesday, March 10, 2010
So it's not a problem of if else (I like lambadas a lot since potentially 10000 lines of code can turn into 1000) but a problem of abstraction. If you like to record a chanel you don't really browse throgh the chanels and then hit record whenever. You record what you like and you probably know in advance what has to be recorded so no need for multy support if the abstraction emulates how a person thinks ... and this is how a person uses and application ... in real life! He/she thinks before recording:) I am a little disappointed that few people see how much programming tries to emulate life and there for whe get somewhat obscured versions every so often of solutions that really do not have question to begin with. back to the example: A person never goes on to record something before knowing what. Now you say what if he's crazy ... well do you support user error ? ... sure just record anything:) So the code above solves a problem that I think does not exist. If your solution is different from:

[user wants to record chanel 1]
[record chanel1]

2 lines of phsedo code ... actually should be aproximatly 2 lines in real code too, then I think your solutions is wrong.

I always try to combine atleast OOP, AOP & functional programming when coding to emulate how I would go about waking up, washing my face, putting my pants on and going to labour. Because I will not wake up and do and if else to put on my pants...

So sorry I think the question discussed here even though not bad (some say creative, I'd say to some extend yes) is irrelavant as there is not a real problem to solve if your design follows the simple logic of the SMART brain over the machine which knows how to invert bits and switch of transistors.
Ivo s Wednesday, March 10, 2010
So it's not a problem of if else (I like lambadas a lot since potentially 10000 lines of code can turn into 1000) but a problem of abstraction. If you like to record a chanel you don't really browse throgh the chanels and then hit record whenever. You record what you like and you probably know in advance what has to be recorded so no need for multy support if the abstraction emulates how a person thinks ... and this is how a person uses and application ... in real life! He/she thinks before recording:) I am a little disappointed that few people see how much programming tries to emulate life and there for whe get somewhat obscured versions every so often of solutions that really do not have question to begin with. back to the example: A person never goes on to record something before knowing what. Now you say what if he's crazy ... well do you support user error ? ... sure just record anything:) So the code above solves a problem that I think does not exist. If your solution is different from:

[user wants to record chanel 1]
[record chanel1]

2 lines of phsedo code ... actually should be aproximatly 2 lines in real code too, then I think your solutions is wrong.

I always try to combine atleast OOP, AOP & functional programming when coding to emulate how I would go about waking up, washing my face, putting my pants on and going to labour. Because I will not wake up and do and if else to put on my pants...

So sorry I think the question discussed here even though not bad (some say creative, I'd say to some extend yes) is irrelavant as there is not a real problem to solve if your design follows the simple logic of the SMART brain over the machine which knows how to invert bits and switch of transistors.
Ivo S Wednesday, March 10, 2010
Sorry for the double, but I actually clicked "Comment" button and it didn't show my comment but it said thanks for your comment. So I hit reload to see if it would appear and it appered twice, as if I hit the "comment" button twice, somekind of postback issue I suppose
gravatar SANCHEZ35Neva Tuesday, September 14, 2010
That is well known that money makes us autonomous. But what to do when somebody does not have money? The only one way is to try to get the loan and just small business loan.
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!