Pretty soon after I started writing Perl in 1994, I noticed that it lacked a construct often found in other languages: the switch statement. Not to worry, though—you can achieve the same effect with a cascading series of if
-elsif
statements, right?
Well, no, you shouldn’t do that, especially if the chain is really long. There’s even a perlcritic policy about it, which suggests that you use given
and when
instead.
But given
and when
(and the smartmatch operator they imply, ~~
) are still considered experimental, with behavior subject to change. So what’s a responsible developer to do?
The answer is to use the for
statement as a topicalizer, which is a fancy way of saying it assigns its expression to $_
. You can then use things that act on $_
to your heart’s content, like regular expressions. Here’s an example:
for ($foo) {
/^abc/ and do {
...
last;
};
/^def/ and do {
...
last;
};
# FALL THRU
...
}
This will cover a lot of cases (haha, see what I did there? A lot of languages use a case
statement… oh, never mind.) And if all you’re doing is exact string matching, there’s no need to bring in regexps. You can use a hash as a lookup table:
my %lookup = (
foo => sub { ... },
bar => sub { ... },
);
$lookup{$match}->();
EDIT: If every alternative is assigning to the same variable, a ternary table is another possibility. This is a chained set of ternary conditional (? :
) operators arranged for readability. I first heard about this technique from Damian Conway's Perl Best Practices (2005).
# Name format # Salutation
my $salute = $name eq '' ? 'Dear Customer'
: $name =~ /(Mrs?[.]?\s+\S+)/ ? "Dear $1"
: $name =~ /(.*),\s+Ph[.]?D/ ? "Dear Dr. $1"
: "Dear $name"
;
Note that although this is just as inefficient as a cascaded-if
/elsif
, it’s more clear that it's a single assignment. It’s also more compact and reads like a table with columns of matches and alternatives.
Any of these patterns are preferable to cascading if
/elsif
s. And if you want to monitor the development of given
, when
, and ~~
, check this issue on GitHub. It was last commented on eight years ago, though, so I wouldn’t hold my breath.
Top comments (5)
Surely
if(/^abc/) {
last;
}
is more readable and in this case shorter (10 vs 13 symbols) than the horrible
/^abc/ and do {
last;
}
But I do agree that the lookup is better... but sometimes the best is the stacked ternaries if all the brace is doing is assigning...
I've amended the post to add an example of stacked ternaries.
Either is fine—I prefer less punctuation, especially in a punctuation-happy language like Perl. And stacked ternaries are great if all you're doing is assigning to the same variable.
Hi Mark,
I tried the suggested for loop with topicalizer in one of my scripts. I am developing a reporting tool in Perl (fetch data from DB and generate excel or html reports, then send them via mail).
I am still new to Perl, so I am writing baby Perl - I think that's the term for that :), and I must apologize for the sometimes cumbersome solutions.
Here is how my envrionment looks like:
There is a Collect.pm package,which initializes the "global" scalar $USECASE:
There is report.pl which import
My::Data::Collect
and assings the value to$USECASE
usingGetOpt::Long
like follows:GetOptions('type=s' => \$USECASE,
'mode=s' => \$MODE);
and then I start the scirpt:
report.pl --type uc1
.Later in the same script I call
get_pss_ael()
subroutine fromMy::Data::Collect
within a for loop, with$USECASE
as topicalizer:Then out of sudden the
$USECASE
scalar isundef
inside the subroutine call. When I check it with say outside the for loop in the main namespace (report.pl) it is there with the value assigned byGetOpt::Long
, inMy::Data::Collect
, outside any code block and subroutine it is there, although when I pass it to a subroutine it is alreadyundef
.Substituting that for construct with if-else solves that problem.
What's going on there?
Could You please provide further explanation?
I hope I described my issue with enough examples, and good code snippets. If not please let me know and I edit.
Thank You!
Kind Regards,
Csaba
Great post 👍 thank you a lot