6.16. Value Switches
Sometimes an if cascade selects its action by testing the same variable against a fixed number of predefined values. For example: sub words_to_num { my ($words) = @_; # Treat each sequence of non-whitespace as a word... my @words = split /\s+/, $words; # Translate each word to the appropriate number... my $num = $EMPTY_STR; for my $word (@words) { if ($word =~ m/ zero | zéro /ixms) { $num .= '0'; } elsif ($word =~ m/ one | un | une /ixms) { $num .= '1'; } elsif ($word =~ m/ two | deux /ixms) { $num .= '2'; } elsif ($word =~ m/ three | trois /ixms) { $num .= '3'; } # etc. etc. until... elsif ($word =~ m/ nine | neuf /ixms) { $num .= '9'; } else { # Ignore unrecognized words } } return $num; } # and later... print words_to_num('one zero eight neuf'); # prints: 1089 A cleaner and more efficient solution is to use a hash as a look-up table, like so: my %num_for = (# English Français Française'zero' => 0, 'zéro' => 0, 'one' => 1, 'un' => 1, 'une' => 1, 'two' => 2, 'deux' => 2, 'three' => 3, 'trois' => 3,# etc. etc.'nine' => 9, 'neuf' => 9, ); sub words_to_num { my ($words) = @_;# Treat each sequence of non-whitespace as a word...my @words = split /\s+/, $words;# Translate each word to the appropriate number...my $num = $EMPTY_STR; for my $word (@words) { my $digit = $num_for{lc $word}; if (defined $digit) { $num .= $digit; } } return $num; } # and later... print words_to_num('one zero eight neuf');# prints: 1089 In this second version, words_to_num( ) looks up the lowercase form of each word in the %num_for hash and, if that look-up provides a defined result, appends it to the number being created. The primary advantage here is that the code in the for loop never need change, no matter how many extra words you subsequently add to the look-up table. For example, if we wished to cater for Hindi digits as well, then you'd need to change the if'd version in 10 separate places: for my $word (@words) { if ($word =~ m/ zero | zéro | shunya /ixms) { $num .= '0'; } elsif ($word =~ m/ one | un | une | ek /ixms) { $num .= '1'; } elsif ($word =~ m/ two | deux | do /ixms) { $num .= '2'; } elsif ($word =~ m/ three | trois | teen /ixms) { $num .= '3'; } # etc. elsif ($word =~ m/ nine | neuf | nau /ixms) { $num .= '9'; } else { # Ignore unrecognized words } } But, in the look-up table version, the only change would be to add an extra column to the table itself: my %num_for = ( Factoring the translations out into a table also improves the readability of the code, both because the code is more compact, and because tables are a familiar and comprehensible way to structure information. The values to be looked up in a table don't have to be scalar constants. For example, here's a simple module that installs a debug( ) function, whose behaviour can be configured when the module is loaded: package Debugging; use Carp; use Log::Stdlog { level => 'debug' }; The module's import( ) subroutine (which is called whenever the module is loaded) takes a string that specifies how the newly created debug( ) subroutine should behave. For example: use Debugging qw( logged ); That string is unpacked into $mode within the import subroutine, and then used as a look-up key into the %debug_mode hash. The look-up returns an anonymous subroutine that is then installed (via the Sub::Installer module) as the caller's debug( ) subroutine. Once again, the advantage is that the logic of the import( ) subroutine doesn't have to change when additional debugging alternatives become available. You can simply add the new behaviour (as an anonymous subroutine) to the %debug_mode table. For example, to provide a debugging mode that counts messages: |