tags:

views:

183

answers:

4

How can I write this with the smart match operator (~~)?

use 5.010;

my $string = '12 23 34 45 5464 46';

while ( $string =~ /(\d\d)\s/g ) {
    say $1;
}
+3  A: 

Interesting. perlsyn states:

Any ~~ Regex pattern match $a =~ /$b/

so, at first glance, it seems reasonable to expect

use strict; use warnings;
use 5.010;

my $string = '12 23 34 45 5464 46';

while ( $string ~~ /(\d\d)\s/g ) {
    say $1;
}

to print 12, 23, etc but it gets stuck in a loop, matching 12 repeatedly. Using:

$ perl -MO=Deparse y.pl

yields

while ($string ~~ qr/(\d\d)\s/g) {
    say $1;
}

looking at perlop, we notice

qr/STRING/msixpo

Note that 'g' is not listed as a modifier (logically, to me).

Interestingly, if you write:

my $re = qr/(\d\d)\s/g;

perl barfs:

Bareword found where operator expected at C:\Temp\y.pl line 5, 
near "qr/(\d\d)\s/g"
syntax error at C:\Temp\y.pl line 5, near "qr/(\d\d)\s/g"

and presumably it should also say something if an invalid expression is used in the code above

Sinan Ünür
@Sinan So I can't this write with the smart match operator?
sid_com
@sid_com I do not think you can but I do not know if that behavior is by design.
Sinan Ünür
I'm guessing there is a bug somewhere in what you ran through Deparse. When I use the code he put in I get this: `while ($string =~ /(\d\d)\s/g)`. And as a matching regex (from perlsyn) it should have the `g` operator. I'd check your perl install. =-[
Jack M.
@Jack M. What I ran through Deparse is what is shown in my post. And the output I get is the same. **And**, I get the same output on Linux and Windows XP with perl 5.10.1.
Sinan Ünür
That was my fault. I was using his original code, not the modified smart match code. You are correct, once you plop in the smart match.
Jack M.
A: 

Is the expected behaviour to output to first match endlessly? Because that's what this code must do in its current form. The problem isn't the smart-match operator. The while loop is endless, because no modification ever occurs to $string. The /g global switch doesn't change the loop itself.

What are you trying to achieve? I'm assuming you want to output the two-digit values, one per line. In which case you might want to consider:

say join("\n", grep { /^\d{2}$/ } split(" ",$string));
RET
I tried to change "=~" with "~~". But since it doesn't work I will use "=~".
sid_com
+1  A: 

If we go and look at what these two variants get transformed into, we can see the reason for this.


  • First lets look at the original version.

    perl -MO=Deparse -e'while("abc" =~ /(.)/g){print "hi\n"}'
    
    while ('abc' =~ /(.)/g) {
        print "hi\n";
    }
    

    As you can see there wasn't any changing of the opcodes.

  • Now if you go and change it to use the smart-match operator, you can see it does actually change.

    perl -MO=Deparse -e'while("abc" ~~ /(.)/g){print "hi\n"}'
    
    while ('abc' ~~ qr/(.)/g) {
        print "hi\n";
    }
    

    It changes it to qr, which doesn't recognize the /g option.

    This should probably give you an error, but it doesn't get transformed until after it gets parsed.


    The warning you should have gotten, and would get if you used qr instead is:

    syntax error at -e line 1, near "qr/(.)/g"


The smart-match feature was never intended to replace the =~ operator. It came out of the process of making given/when work like it does.

Most of the time, when(EXPR) is treated as an implicit smart match of $_.
...

Brad Gilbert
That was my idea - replace "=~" with "~~". Does Perl6 have only the smart match operator or will there be a "=~" to?
sid_com
A: 

To be honest, I'm not sure you can use the smart match operator for this. In my limited testing, it looks like the smart match is returning a boolean instead of a list of matches. The code you posted (using =~) can work without it, however.
What you posted doesn't work because of the while loop. The conditional statement on a while loop is executed before the start of each iteration. In this case, your regex is returning the first value in $string because it is reset at each iteration. A foreach would work however:

my $string = '12 23 34 45 5464 46';
foreach my $number ($string =~ /(\d\d)\s/g) {
    print $number."\n";
}
Jack M.
Completely wrong and off the mark. First, using `$string =~ s///g` in a `while` loop is not only perfectly OK but is preferable to a `for` loop in a lot of instances. Second, you are missing the fact that the OP is talking about the smart match operator `~~` and not `=~`.
Sinan Ünür
He's not using `s///g`, though. He's using, essentially, `m///g`. You are correct about the smart operator, I'll edit to fix that bit up.
Jack M.
I meant `m//` and what I said about `s///` applies to `m//` in `while` versus `for` loops as well.
Sinan Ünür
Unless you are manipulating (in this case) `$string` inside the loop, are you not creating an infinite loop? In the case of the OP's question, that is why his code didn't output what he expected, is it not?
Jack M.
No. You are not creating an infinite loop precisely because of the `g` modifier. Try `while ($string =~ /(\d{2})\s/g ) { print "$1\n" }`. The difference between `while (m//g)` and `for (m//g)` is that you get to operate on matches one-by-one with `while` (like reading a file line by line versus slurping it).
Sinan Ünür
Huh, that I did not know. I head read forever ago in The Perl Cookbook that the while was evaluated each time, and that an m// would still get evaluated at each instance. Good to know.
Jack M.