Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask

Avoiding if/else knots

by loris (Hermit)
on Aug 18, 2006 at 12:22 UTC ( #568119=perlquestion: print w/replies, xml ) Need Help??
loris has asked for the wisdom of the Perl Monks concerning the following question:

Faced with the prospect of mapping a dozen or so cases defined by half a dozen attributes on to two or three actions which just set flags or scalars, I thought that, rather than tacking more ifs and elses onto the existing code, it might be a good idea to use a hash. I would have to convert relevant combinations of attributes into a form that I could use as a hash key and the corresponding values for the resulting actions into a hash value.

Has anyone used this approach before and knows of any caveats? Or does anyone have a better idea? I should say that I shall actually be implementing this in Java, so I won't be able to make use of any particularly Perly solutions, although I would still be insterested in them.



"It took Loris ten minutes to eat a satsuma . . . twenty minutes to get from one end of his branch to the other . . . and an hour to scratch his bottom. But Slow Loris didn't care. He had a secret . . ."

Replies are listed 'Best First'.
Re: Avoiding if/else knots
by Limbic~Region (Chancellor) on Aug 18, 2006 at 12:53 UTC
    You probably want to take a look at Implementing Dispatch Tables. The trouble with dispatch tables in Perl is that the conditions need to be exact keys. In other words, if you want any value integer in the range 1-999 to do the same thing, you need to create keys for each of those integers. There are ways around this in certain situations which is why I suggest reading that tutorial.

    Update: s/value/integer/ pointed out by xdg++

    Cheers - L~R

      The trouble with dispatch tables in Perl is that the conditions need to be exact keys. In other words, if you want any value in the range 1-999 to do the same thing, you need to create keys for each of those integers.

      If you need integers in a range, you've still got a discrete set of values and mapping that to the dispatch table isn't hard.

      use strict; use warnings; sub action_1_to_999 { print "1 to 999\n"; } my %dispatch = ( 0 => sub { print "Zero\n" }, map { $_ => \&action_1_to_999 } ( 1 .. 999 ), 1000 => sub { print "1e3\n" }, ); $dispatch{0}->(); $dispatch{23}->();

      The challenge is for non-integer conditions or non-bounded ones. What if N > 1000? What if N = 3.14159265358979323? What if N = 'PI'? In that case, you still need to have the advance logic, if/else or otherwise, to convert inputs to a smaller number of well-defined cases.

      My advice in that case is keep the case logic in one place and the actions all somewhere else via subroutines. Dispatch tables probably don't help unless you're already starting with a discrete, bounded set of inputs (or need to dynamically add cases as your program runs).


      Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

        When dealing strictly with small positive integers like in your example, an array might be better than a hash.

        sub action_1_to_999 { print "1 to 999\n"; } my @dispatch = ( sub { print "Zero\n" }, map { \&action_1_to_999 } ( 1 .. 999 ), sub { print "1e3\n" }, ); $dispatch[0]->(); $dispatch[23]->();
        No offense, but you really haven't added anything to what I said.

        To quote myself, "The trouble with dispatch tables in Perl is that the conditions need to be exact keys". I didn't say that creating a range was hard, I said "you need to create keys for each of those integers".

        In other words, there is no short-cut to make that a single key and anything that can't be done as a straight lookup (requires logic) doesn't work. That is, it doesn't work without additional if/elsif/else knots to determine the correct key to dispatch.

        Cheers - L~R

      I have handled this problem before by having an array of pairs of functions - the first tests whether this is the right case, the other does it. You dispatch something like this:
      for my $case (@cases) { if ($case->[0]->(@data)) { $case->[1]->(@data; last; } }
      and then you just put whatever you want into @cases.

      This is for more complex code cleanup. For instance suppose one might have a number of possibilities which need to be included only if some global condition is true. The standard if/elsif/else construct would require testing that condition in lots of elsifs. Lots of repeated code. This approach allows you to replace that with testing that condition once, then shoving a bunch of stuff into @cases if it is true.

      Note that this is not a common need. It should not be a normal part of your toolbox. I think I've only resorted to the technique 2 or 3 times.

      My approach to that problem would be to write function to map the input to the keys in the hash. Something like
      $dispatch{calculate_key{$input}}->(%attr); sub calculate_key { #.. return $key; }

      holli, /regexed monk/
        Sure except that calculate_key() is going to contain the same if/elsif/else knot that the OP is asking to avoid. The bottom line is that if your conditions are not exact keys then using dispatch tables will not avoid the knot. That isn't to say that it can't help make the code cleaner.

        Cheers - L~R

Re: Avoiding if/else knots
by izut (Chaplain) on Aug 18, 2006 at 12:28 UTC

    I think that is a common practice in Perl. I always write code like this:

    my %functions = ( 'this' => sub { ... }, 'that' => \&do_something, ); my %attrs = (...); my $key = 'this'; $functions{$this}->(\%attrs);

    Hope this helps.

    Igor 'izut' Sutton
    your code, your rules.

Re: Avoiding if/else knots
by Fletch (Chancellor) on Aug 18, 2006 at 12:39 UTC

    The technique's perfectly valid and is usually known as a 'dispatch table' (if you're looking for a search term).

Re: Avoiding if/else knots
by jdporter (Canon) on Aug 18, 2006 at 19:52 UTC
      switches are awesome for simple short cases. hashes for complex ones. pick yer poison. :)

        I could say the reverse as well. Hash-based dispatch tables have a significant limitation: they're hash based. That means any condition you look up by has to be a hash key, which means it (or its stringification) has to exist in the hash. Of course, you can increase your flexibility in this area by using some of the funky TIEHASH modules available on CPAN, such as Tie-Hash-Approx, Tie-Hash-KeysMask, Tie-Hash-Regex, and Tie-RangeHash. But in the general case, I believe switches are more powerful because (at least in the typical Perl implementations) there are no limitations on the conditions. Switches also give you defaults, which can be hard to do with hashes without the aid of special tie modules, and fallthrough, which is even harder. Try writing this using a dispatch table:

        use Switch::Perlish; switch $var, sub { case sub { $_[0] > 9 }, sub { warn "$var>9"; fallthrough }; case 9, \&found_it; default \&not_found; };
        We're building the house of the future together.
Re: Avoiding if/else knots
by Codon (Friar) on Aug 21, 2006 at 17:00 UTC
    I recently had to tackle something similar (yet different) in my work. I had half a dozen conditions to check and various combinations resulted in counting items in different ways. I knew that there had to be a better way than having a page of if/elsif/else (actually, it was a nested ternary, but it functioned the same).

    After spending a while thinking about it (load the problem into memory; take it home; let it run in the background) I saw that at the heart of the problem I was filtering things. I don't know if that's quite the same for you (not enough details in the OP). I essentially had a request that was looking for things that had a certain set of attributes. I was comparing the attributes of the request to the attributes of each item in the list to find matches and count matches / misses accordingly. I worked up a solution that defined a record filter by setting up a bit mask indicating which attributes I was looking for. Then as I looked at each record that I needed to filter, I set bit flags according to the attributes of the record. Then a bit-wise & told me if I got a match.

    Assuming that this even comes close to resembling your problem, you could use the discrete bit-wise results as keys in you dispatch table. The values would be the methods to dispatch to for each case.

    Ivan Heffner
    Sr. Software Engineer, DAS Lead, Inc.
Re: Avoiding if/else knots
by EvanK (Chaplain) on Aug 18, 2006 at 18:11 UTC
    disregard my post, i misread the OP.

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://568119]
Front-paged by tinita
[erix]: then you might as well send that patch to the DBIC guys :)
[Corion]: erix: Yeah, I just found that it has no documentation at all on how to circumvent/ eliminate "1+n SELECTs" by building a local hash... I guess I have to make ->has_many do the hash lookup instead of doing the SQL query. But as the problem ...
[Corion]: ... has only manifested itself so far through the puzzled questions of other bystanders, I won't go deeper at this time. But the DBIx::Class documentation could well do with a document on how to make "it" (that is, ORMs in general) faster ;)
[Corion]: I find that DBIx::Class, like most ORMs makes things easy until they become performance critical and then makes it horribly hard to change things because the design is highly inflexible if you don't already know about the problems of 1+n :-/

How do I use this? | Other CB clients
Other Users?
Others taking refuge in the Monastery: (7)
As of 2017-09-25 11:06 GMT
Find Nodes?
    Voting Booth?
    During the recent solar eclipse, I:

    Results (279 votes). Check out past polls.