est has asked for the wisdom of the Perl Monks concerning the following question:

Hi Mongers,

I've tried to follow Damian's Best Practice to avoid "cascading if" as much as possible.
However I can't seem to find a good solution for the code snippet below?

if ($line =~ m{\A Job: }xms) { $job_details_info{Job} = $line; } elsif ($line =~ m{\A File: }xms) { push @files, $line; } elsif ($line =~ m{\A Image }xms) { $job_details_info{Image} = $line; }
Anyone can shed a light for me please?

Cheers,
Blooky

Replies are listed 'Best First'.
Re: Multiple If Selections
by kyle (Abbot) on Jan 07, 2008 at 04:52 UTC

    Sometimes if ... elsif ... else really is the clearest, most effective way to do what you want. The code you've posted, I'd probably leave as-is, and go to something more advanced only as it got more hairy.

    In Perl 5.10, you've got given ... when, which wold work really well here.

    given ($line) { when ( m{\A Job: }xms ) { $job_details_info{Job} = $line } when ( m{\A File: }xms ) { push @files, $line } when ( m{\A Image }xms ) { $job_details_info{Image} = $line } }

    Since your conditions are all patterns, a straight dispatch table might not be the way to go. I might write something like this:

    use List::Util qw( first ); my @disp = ( { pattern => qr{\A Job: }xms, action => sub { $job_details_info{Job} = shift }, }, { pattern => qr{\A File: }xms, action => sub { push @files, shift }, }, { pattern => qr{\A Image }xms, action => sub { $job_details_info{Image} = shift }, } ); my $todo = first { $line =~ $_->{pattern} } @disp; if ( defined $todo ) { $todo->{action}->( $line ); } else { warn "no match: $line"; }

    That's not really that much better than elsif, in my opinion, but it makes it possible to add more data to each case that it's testing, and you can then pass the data structure around to make it serve multiple purposes. For example, you might have a menu with user-visible text, conditions, and actions, and different parts of your program use different pieces of data, all in one structure that you can edit in one place.

Re: Multiple If Selections
by McDarren (Abbot) on Jan 07, 2008 at 04:07 UTC
    Looks like a job for a dispatch table.

    There is a very good tutorial on this subject by Roy Johnson that I personally found quite useful.

    Hope this helps,
    Darren :)

      I must disagree. With only three conditions, all of which have very small associated blocks, there's no reason not to use a simple if/else structure. A dispatch table is fine when there are, say, five or more conditions, IMHO. But any less, and you're adding an extra layer of complexity for no reason.
        Well, yes.

        However, in my (somewhat limited) experience I've found that these things tend to grow over time, and before you know it you have to add another test condition, and then another, ....

        So whilst I agree with what you say, the point of my response was to make the OP aware of dispatch tables as an option for when things start to get a little unwieldy.

        Cheers,
        Darren :)

Re: Multiple If Selections
by bobf (Monsignor) on Jan 07, 2008 at 04:21 UTC

    As McDarren mentioned, a dispatch table is probably the way to go, and you can make it as complex or as simple as you wish (depending on your data). See below for a couple of examples.

    use strict; use warnings; my %dispatch = ( Job => \&job, File => \&file, Image => \&image, # Job => \&add_to_hash, # alternative to job/image subs ); my %job_details_info; my @files; foreach my $line ( 'Job line', 'File line', 'Image line' ) { # use a dispatch table if( $line =~ m/(Job|File|Image)/ ) { $dispatch{$1}->($line); } # group functionality if( $line =~ m/(Job|Image)/ ) { $job_details_info{$1} = $line; } elsif( $line =~ m/File/ ) { push @files, $line; } } sub job { my ( $line ) = @_; $job_details_info{Job} = $line; } sub file { my ( $line ) = @_; push @files, $line; } sub image { my ( $line ) = @_; $job_details_info{Image} = $line; } # you could combine the job and image subs into something like this sub add_to_hash { my ( $key, $line ) = @_; $job_details_info{$key} = $line; }

      If you're going to go with this approach to setting up/executing the dispatch, one suggestion:

      Change

      if( $line =~ m/(Job|File|Image)/ )
      to
      my $re = join '\|', keys %dispatch; if ( $line =~ m/$re/ )
      so that you won't have to manually update the regex every time a case is added/removed/renamed. Changing the dispatch table and forgetting to update that regex would otherwise seem like a very easy way to introduce bugs which will leave you scratching your head for hours as you stare at the dispatch table and the called sub trying to figure out why they don't work together.

        Excellent suggestion++. You forgot the capturing parentheses, though, so instead of wondering why the dispatch table and the subs are working together you'll be wondering why $1 isn't what you think it is. :-)

        Change

        my $re = join '\|', keys %dispatch; if( $line =~ m/$re/ )
        to either
        my $re = '(' . join( '|', keys %dispatch ) . ')'; if( $line =~ m/$re/ )
        or
        my $re = join( '|', keys %dispatch ); if( $line =~ m/($re)/ )

Re: Multiple If Selections
by ysth (Canon) on Jan 07, 2008 at 04:37 UTC
Re: Multiple If Selections
by est (Acolyte) on Jan 07, 2008 at 06:30 UTC
    All,

    Thanks for all your responses. I should have mentioned earlier that the complete codes has many more conditions than the snippet I provided.
    That said, the suggestions to use dispatch table seems reasonable to me...

    I tend to think that the switch "given" in 5.10 (5.9.4) as suggested by Kyle is clearer anyhow. But it'll take a while before my employer upgrade the perl version in production :-)

    Thanks again!
       Blooky