Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to replace an if-elsif-else in Perl with something better?

I want to build a bunch of Perl subrotines that all have the same template if elsif elsif else that takes a decision based on a factor variable. Here's an example of subroutine template:

sub get_age{

  my $factor=shift;

  if    ($factor == 1 ){ print "do something" }
  elsif ($factor == 2 ){ print "do somthing2" }
  elsif ($factor == 3 ){ print "do somthing3" }
  elsif ($factor == 4 ){ print "do somthing4" }
  else                 { print "error"        }
  }

I am wondering if there some design pattern on Perl to replace the if else condition with more elegant solution which easy to maintain in the future specifically if I need to change some of the conditions or delete some of it?

like image 944
smith Avatar asked Nov 29 '11 22:11

smith


2 Answers

A couple of people have mentioned a dispatch table. There are two things and it's nice to keep them apart sometimes. There's the list of possible things that could happen, and the thing that makes them happen. If you couple the two, you're stuck with your solution. If you keep them separate, you have more flexibility later.

The dispatch table specifies the behavior as data instead of program structure. Here's two different ways to do it. With your example you have integers and something like that might use an array to store things. The hash example is the same idea but looks up the behavior slightly differently.

Also notice that I factor out the print. When you have repeated code like that, try to move the repeated stuff up a level.

use v5.10;

foreach my $factor ( map { int rand 5 } 0 .. 9 ) {
    say get_age_array( $factor );
    }

my @animals = qw( cat dog bird frog );
foreach my $factor ( map { $animals[ rand @animals ] } 0 .. 9 ) {
    say get_age_hash( $factor );
    }

sub get_age_array {
    my $factor = shift;

    state $dispatch = [
        sub { 'Nothing!' }, # index 0
        sub { "Calling 1" },
        sub { 1 + 1 },
        sub { "Called 3" },
        sub { time },
        ];

    return unless int $factor <= $#$dispatch;

    $dispatch->[$factor]->();   
    }


sub get_age_hash {
    my $factor = shift;

    state $dispatch = {
        'cat'  => sub { "Called cat" },
        'dog'  => sub { "Calling 1"  },
        'bird' => sub { "Calling 2, with extra" },
        };

    return unless exists $dispatch->{$factor};

    $dispatch->{$factor}->();   
    }
like image 135
brian d foy Avatar answered Nov 07 '22 17:11

brian d foy


Update: Make sure you read brian's comment below; basically, it's better to use for instead of given, due to various issues he comments on in his link. I've updated my advice to incorporate his improvements, which he outlines in Use for() instead of given():

If you're on perl 5.10 or newer, given/when is the magic pair you are looking for, but you really should use for/when instead.. Here's an example:

use strict;
use warnings;
use feature qw(switch say);

print 'Enter your grade: ';
chomp( my $grade = <> );

for ($grade) {
    when ('A') { say 'Well done!'       }
    when ('B') { say 'Try harder!'      }
    when ('C') { say 'You need help!!!' }
    default { say 'You are just making it up!' }
}
like image 40
Marius Kjeldahl Avatar answered Nov 07 '22 18:11

Marius Kjeldahl