Sunday 6 March 2016

The Perl flip-flop operator

Have you ever wondered where fashion and software development overlap? If so, look no further than the flip-flop. It's a feature available in Sed, Awk, Ruby and Perl which - akin to its namesake - is short, revealing and can raise a few eyebrows.
Robot Gear flip-flops by Cafepress

You're probably familiar with the range operator '..' where this statement

my @foo = (3..10);

creates an array @foo containing (3, 4, 5, 6, 7, 8, 9, 10). But if instead you accidentally write:

my $foo = (3..10);

you'll stumble on this error message:

Use of uninitialized value $. in range (or flip) at...

Do you wonder why the compiler worries that you're going to flip? Think again. It means you've used the flip-flop operator.

What's the flip-flop operator you ask? As a symbol, the flip-flop is the same as the range operator, but when used in scalar context it's a 'boolean' along the lines of 'Am I caught between these truths?'.

It's much easier to show by example. Consider this "flip-free" code where you want to print everything enclosed in 'f' and 'zle'.

my @array = qw/frizzle this fit sizzle bit/;
my $switch = 0;
foreach my $word (@array) {
  if ($switch) {
    say $word;

    # switch off if we see the end delimiter
    $switch = $word !~ m/zle/;
  }
  else {
    $switch = $word =~ m/f/;
    if ($switch) {
      say $word ;
      $switch = $word !~ m/zle/;
    }
  }
}


it has the output

  frizzle
  fit
  sizzle

Here's the equivalent code using the flip-flop operator:

foreach (@array) {
    say if (/f/../zle/);
}

or to be more explicit:

foreach my $word (@array) {
    say $word if ($word =~ m/f/ .. $word =~ m/zle/);
}

Reading the "flip-free" code above, you'll see that there were three places where the switch was changed - "frizzle" (on and off), "fit" (on) and "sizzle" (off).

Now what if you decide that you don't want the switch flicked twice in the same match. Instead, you want the output
frizzle
this
fit
sizzle
Easy - just change the two-dot flip flop to the three-dot flip-flop:
my @array = qw/frizzle this fit sizzle bit/;

foreach (@array) {
  say if (/f/ ... /zle/);
}

Or fliplessly:
my @array = qw/frizzle this fit sizzle bit/;

my $switch = 0;
foreach my $word (@array) {
  if ($switch) {
    say $word;
    $switch = $word !~ m/zle/;
  }
  else {
    $switch = $word =~ m/f/;
    say $word if $switch;
  }
}

Finally - don't imagine we can't extract a little bit more meaning out of a few silly dots. This time they are flip-flopping on line numbers:
while (<DATA>) {
  print if (3..5)
}

__DATA__
One
two - buckle shoe
three
four - knock on door
five
six - pick up sticks
which outputs
three
four - knock on door
five
More explicitly:
while (my $line = <DATA>) {
  print $line if $. >= 3 && $. <= 5;
}
where $. is the line number.

I'm not the first person to go dotty over dots. Now you understand how they work you'll see how experts like Dave Cross, brian d foy and the Grandfather monk of the monastery put them to good use.

p.s. It's a little less subtle in Perl 6 - but easier to read!



4 comments:

  1. Eventually there may need to be hordes of Perl programmers trained to operate and maintain Ghost Perl Webserver artificial intelligence.

    ReplyDelete
  2. The code:

    while () {
    print if (3..5)
    }

    with the __DATA__ included, doesn't work on Windows 7.
    I'm using perl 5.18.1

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Thanks Cesar, You've found an error in the article which I've now corrected. It's the challenge of writing code in Blogger:) The code should be

    while (<DATA>) {
    print if (3..5)
    }

    Does that now work for you?

    ReplyDelete