Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

Flipin good, or a total flop?

by GrandFather (Cardinal)
on Jan 25, 2006 at 09:44 UTC ( #525392=perltutorial: print w/ replies, xml ) Need Help??

When I first encountered the flip-flop operator (scalar context range operator) while browsing through the Camel I thought: that's interesting, I wonder what it's good for? I then read quickly through the rest of the section before my eyes glazed over, without learning much more. Which is odd really given my electronics background!

Since then I have seen mention of the flip-flop operator a number of times in various contexts here - either as a very tidy solution to a parsing problem, or as the villain in what looks like a slice (but ain't).

A recent SoPW where the villain was a flip-flop operator pretending to be a slice range piqued my interest so I decided to go back to the Camel for enlightenment. This meditation is a result of that consultation and a little subsequent playing with flip-flops. Follow along with me as I share what I have learned. Keep a copy of Perl handy, there's code to try.

First, if you think that a "scalar context" is a cliff face, you should consider taking up knitting and leave the Perling to others. Similarly, you should clearly understand that a "list context" is not in a boat in a high wind! The flip-flop operator (oh bother it, let's use f/f) only applies in a scalar context, otherwise it is a range operator.

The key to understanding the f/f is that it remembers its state. There are in effect three parts to the f/f: the left expression (trigger), the right expression (reset), and the flip-flop that stores the f/f's state (a flip-flop is an electronic device that remembers the state of one bit).

Let's have a look at one of the beasties in its natural environment to see what it does and where the various parts are:

while (<DATA>) { print "\t" if (/^start/ .. /^end/); print $_; } __DATA__ First line. start Indented line end Back to left margin

This prints:

First line. start Indented line end Back to left margin

The left expression (/^start/) is the trigger and the right expression (/^end/) is the reset. You can't see the flip-flop that stores the operator's state, but you could pretend the .. is where it is if you like. When the program starts the flip-flops for all the f/fs are reset (set to 0 or false). So the first time through the loop above the f/f evaluates its trigger as false and remains reset. For the second line the trigger expression evaluates true and the f/f is set true. For the third line the reset expression is false so the f/f remains triggered. For the fourth line the reset expression is true so the f/f is reset. For the last line the trigger expression is false so the f/f remains reset.

Note that while the f/f is reset (false), only the trigger expression is evaluated and that while the f/f is set (true) only the reset expression is evaluated. There are some subtleties of course - even discounting context issues. For example, what happens when both the trigger and reset expressions are true at the same time?

Consider the following:

while (<DATA>) { print "\t" if (/start/ .. /end/); print $_; } __DATA__ First line. Indent lines between the start and the end markers Back to left margin

This prints:

First line. Indent lines between the start and the end markers Back to left margin

Ok, why? The f/f returns true if the flip flop had been, or becomes set when it is evaluated. It doesn't matter if the flip flop is reset in the same evaluation cycle as it became set. That is: even if the flip flop is set and reset (as in the code above) in the same evaluation cycle, it still returns a true result.

But what if you don't want the reset detected in the same line? Use the three hump Camel. Uh, no no, I mean the three dot f/f:

while (<DATA>) { print "\t" if (/start/ ... /end/); print $_; } __DATA__ First line. Indent lines between the start and the end markers So this is indented, and this is the end of the indented block. Back to left margin

Which prints:

First line. Indent lines between the start and the end markers So this is indented, and this is the end of the indented block. Back to left margin

That's the flip side dealt with. What about the darker flop side? It turns out that there is some good even in the evil side of the f/f. In an endeavor not to let any semantic wrinkle be wasted, Perl ascribes special meaning to numeric constants used as the set and reset expressions for the f/f. If a constant is used, it is compared with the current file line number (special variable $.) and the result is true if they match:

while (<DATA>) { print "\t" if (2 .. 4); print $_; } __DATA__ First line. start Indented line end Back to left margin

This prints:

First line. start Indented line end Back to left margin

Well, there's a useful thing eh! No problem there then. Just use it when you need it (about once every 1e6 lines of code) and forget about it otherwise right? Wrong! But before we take a look at why, let's make a little detour and look at the range operator: .. (in list context).

Remember that .. is really two completely different operators depending on context. The range operator returns a list of constants between two limits:

print join ' ', (1..5);

prints:

1 2 3 4 5

for example. It is often used to slice arrays like this:

my @array = qw(1 2 3 4 5 6 7 8 9); print "@array[0..3]";

which prints:

1 2 3 4

Ok, with that very brief refresher course for the range operator in mind, consider:

my @array = qw(1 2 3 4 5 6 7 8 9); print "$array[0..3]";

What gets printed? Most likely "2", sometimes "1". By the way, did you spot the deliberate mistake? $array puts the [] into scalar context and makes .. the f/f operator, not the range operator a first glance would suggest. And there the dark side of the f/f is revealed and reviled.

Because the context can be rather subtle and because the f/f with constant numeric values for its operands looks like a range, the f/f poses a rather nasty trap lying in wait for the unwary. The best defence against the sprung trap is use warnings which will generate a warning, sometimes:

use strict; use warnings; my @array = qw(1 2 3 4 5 6 7 8 9); print "$array[0..3]";

generates the warning:

Use of uninitialized value in range (or flip) at noname.pl line 4.

The f/f can be a really useful tool. It is a great pity that it looks identical with the range operator. But now that you know, you will never make the mistake again will you? And you will remember to use warnings won't you?

Note that a little searching will turn up plenty of other stuff about the scalar range operator, including The Scalar Range Operator. But it doesn't hurt to revisit such stuff from time to time with a different emphasis each time.

Update: added missing code fragment.

Update: fix various spelling errors.


DWIM is Perl's answer to Gödel

Moved to Tutorials by planetscape

( keep:1 edit:14 reap:0 )

Comment on Flipin good, or a total flop?
Select or Download Code
Re: Flipin good, or a total flop?
by ysth (Canon) on Jan 25, 2006 at 11:06 UTC
    A common thing with flipflop is to want to exclude one or both endpoints. To do this, you need to actually check the scalar value returned by the .. operator; it will be a number beginning at 1 when the flip condition is met and increasing once each time thereafter, with an "E0" appended when the flop condition is met. (False is returned as "".)

    Anyway, here are some examples. Better suggestions greatly encouraged.

    $ cat data initial start interior end final $ # Include both endpoints $ perl -wlne'print if /start/../end/' data start interior end $ # Exclude starting point $ perl -wlne'print if ((/start/../end/) || 0) > 1' data interior end $ # Regex alternative for exclude starting point $ perl -wlne'print if (/start/../end/) =~ /^(?!1(?!\d))\d/' data interior end $ # Exclude ending point $ perl -wlne'print if (/start/../end/) =~ /^\d+$/' data start interior $ # Exclude both endpoints $ perl -wlne'print if (/start/../end/) =~ /^\d+(?<!^1)$/' data interior

      Thank you. I intended to mention that and forgot about it :(

      Note that selected itterations, not just the start and end points, can be handled as special cases using the FF return value.


      DWIM is Perl's answer to Gödel
      If you're really interested on whether the start condition and/or end condition were triggered, there's another way: using variables to capture the test results. Like this:
      if((my $start = /start/) .. (my $end = /end/)) { ...
      Later, you simply have to test the boolean value of $start and $end:
      print "First time\n" if $start; print "Last time\n" if $end;
      but only in the if BLOCK, of course (lexical scope for the variables).
        I'm pretty sure that works the same way
        my $i = 13 if $expr;
        does, and is not to be trusted to continue to do what you mean.

        No, maybe I'm wrong. Hmm.

        Update: as long as you don't modify $start or $end except in cases where they were already set, I think you can count on this continuing to work, even if the my $foo if bar thing is "fixed". But I'd expect that if a warning is added for the my $foo if bar thing, that you'd get the warning with your code also.

Re: Flipin good, or a total flop?
by eyepopslikeamosquito (Canon) on Jan 25, 2006 at 21:43 UTC
Re: Flipin good, or a total flop?
by Anonymous Monk on Jul 23, 2013 at 14:52 UTC

    Great tutorial, lots of useful information that was not taught to me when I initially started perl.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perltutorial [id://525392]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others scrutinizing the Monastery: (6)
As of 2014-07-23 05:13 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    My favorite superfluous repetitious redundant duplicative phrase is:









    Results (133 votes), past polls