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
var channelsToRecord = channels
.Where(c => recordingRules.Any(...));
foreach (var channel in channelsToRecord) ...
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...>.
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.
if (condition1 || condition2 || condition3)
{
Record(channel);
}
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.
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?
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.
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);
}
}
}
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.
As I read through Surf2 I was thinking of a very similar approach to what I was about to scroll down to in Surf3.
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.
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.
{
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;
}
}
}
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
[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.
[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.