DEV Community

Matthew O. Persico
Matthew O. Persico

Posted on

Yet Another Perl Switch Statement

I am in the middle of a project at my job where we are converting some Perl to Python :-(. In the conversion I was explaining this invocation of a switch statement:

for (ref($thing)) {
    /ARRAY/ && do {
        some_array_thing($thing);
        last;
    };
    /LIST/ && do {
        some_list_thing($thing);
        last;
    };
    ## default;
    some_scalar_thing($thing);
}
Enter fullscreen mode Exit fullscreen mode

After explaining how the for statement sets $_, I was asked, "Why not just set $_?"

Indeed, why not? You can play with the following code here: http://tpcg.io/KN9H82

use strict;
use warnings;

{
    local $_ = 'foo';
    /bar/ && do { print 'we got bar'; last };
    /foo/ && do { print 'we got foo'; last };
    /eek/ && do { print 'we got eek'; last };
    print 'we dropped through';
}
Enter fullscreen mode Exit fullscreen mode

with output

we got foo
Enter fullscreen mode Exit fullscreen mode

So, my questions are:

  • Why have I never seen this in any discussion of Perl's switch synonyms?
  • Is there any inherent problem with this?

Discussion (14)

Collapse
yukikimoto profile image
Yuki Kimoto

Do you know Syntax::Keyword::Match? If you are interested in switch statement, you can get inspiration from this module.

use v5.14;
use Syntax::Keyword::Match;

my $n = ...;

match($n : ==) {
   case(1) { say "It's one" }
   case(2) { say "It's two" }
   case(3) { say "It's three" }
   case(4), case(5)
           { say "It's four or five" }
   default { say "It's something else" }
}
Enter fullscreen mode Exit fullscreen mode
Collapse
matthewpersico profile image
Matthew O. Persico Author

Not exactly what I'd want. I'd rather see:

match($n) {
   case(1 : ==) { say "It's one" }
   case(2 : ==) { say "It's two" }
   case(3 : ==) { say "It's three" }
   case(4 : >=) { say "It's four or greater" }
   default { say "It's something else" }
}
Enter fullscreen mode Exit fullscreen mode
Collapse
yukikimoto profile image
Yuki Kimoto

If you want to write so, Syntax::Keyword::Match is insufficient.

Collapse
matthewpersico profile image
Matthew O. Persico Author

No I did not, but I will investigate. Thank you.

Collapse
mjgardner profile image
Mark Gardner

That’s exactly what for does, except it’s lexical instead of local.

Perl v5.10 introduced support for explicitly lexical $_, but it was removed in v5.24 after being relegated to experimental status in v5.18. (See that last link for the issues it introduced.)

Collapse
matthewpersico profile image
Matthew O. Persico Author

Right, but the Python person I was talking to said that for their reading, the explicit assignment to $_ made a lot more sense to him than implying it from a for loop. Again, a Python person, "explicit is better than implicit". However, to be honest, I like explicit setting of $_ better than a for loop. So much cleaner IFF you really are using only one value. In fact, I would improve this code with

SWITCH: {
    local $_ = 'foo';
...
Enter fullscreen mode Exit fullscreen mode

And I think I avoid the lexical issues with the local modifier, yes?

Collapse
mjgardner profile image
Mark Gardner

Sure, use a label and then the last statement of your conditions can say last SWITCH;.

If you like your Perl more “Pythonic” then go for it. Personally I prefer to adopt language idioms rather than adapt one language to another. Similar arguments have occurred about C-style for ( ; ; ) loops vs. foreach loops that eschew an index variable when it’s not necessary.

There’s an old saying: “You can write FORTRAN in any language.” The Sapir-Whorf hypothesis says that language influences thought. Limiting yourself to the idioms of one language means limiting yourself to solutions that can be expressed in it, and that doesn’t make sense if you’re actually using a different language.

Thread Thread
matthewpersico profile image
Matthew O. Persico Author

Agreed. But some idioms can be improved. I think the explicit $_ assignment is better than the side-effect assignment via a for (each) statement that has only one target. And yes, in the production code, I added a SWITCH: label. However I did not change last; to 'last SWITCH;; I think that's too verbose, and it doesn't impedance-match withbreakin traditionalswitch` statements.

Collapse
grinnz profile image
Dan Book • Edited on

The issue is not that it's lexical, but that assigning to a lexical $_ instead of the superglobal $_ broke assumptions that people make that $_ can be used in other code scopes dynamically, such as subroutines you call. If it had started out being lexically aliased by foreach and map it would lead to safer code overall.

local assignment does avoid some danger of action at a distance from aliasing with foreach; in the following code, if some_sub were to assign anything to $_ it would clobber $var as well.

my $var = 'foo';
foreach ($var) {
   some_sub and last;
}
Enter fullscreen mode Exit fullscreen mode

In the end I always recommend using a few more keystrokes and avoiding $_ for this entirely.

Thread Thread
matthewpersico profile image
Matthew O. Persico Author

I whipped up two quick tests:
gist.github.com/matthewpersico/aa1... and gist.github.com/matthewpersico/aa1... and yes, not having that local can be disastrous. So, since for and map and the like are coded "properly", i.e., they localize $_, you should be safe to use them and use functions that call them. But if someone has done this $_ assignment trick and forgets the local, there be dragons.

Collapse
iamalnewkirk profile image
Al Newkirk
package main;

use Venus::Match;

my $match = Venus::Match->new(5);

$match->when(sub{$_ < 5})->then(sub{"< 5"});
$match->when(sub{$_ > 5})->then(sub{"> 5"});

$match->none(sub{"?"});

my $result = $match->result;

# "?"
Enter fullscreen mode Exit fullscreen mode

github.com/cpanery/venus/blob/mast...

Collapse
matthewpersico profile image
Matthew O. Persico Author

Oh, I'll bite (resisting the pun): Why "Venus"?

Collapse
jacoby profile image
Dave Jacoby

I was going to say that you can't get fall-through with /match/ && do {...}, but then I started thinking "When was the last time I wanted fall-through? That wasn't parlor-trick code?"

Collapse
mjgardner profile image
Mark Gardner

You get fall-through if you omit last.