http://www.perlmonks.org?node_id=11144131

sherab has asked for the wisdom of the Perl Monks concerning the following question:

Monks, I come seeking wisdom and am confused by a statement I see.... I inherited a perl code project, and this statement in this foreach loop is confusing me... $chapter will end up being something like "5","7", and "11V"....

foreach my $chapter (.....){ ( $chapter =~ /5/ ) and ( $chapter = 5 ); ...... ...... }
The second part of the "and" seems to be assigning "5" to $chapter but I'm normally used to the first part as being "If $chapter matches 5 then do something". Please accept my apologies ahead of time because this may be crazy simple but what is this statement doing?

Replies are listed 'Best First'.
Re: Purpose of =~ and = in this statement
by haukex (Archbishop) on May 23, 2022 at 17:40 UTC
    ( $chapter =~ /5/ )  and ( $chapter = 5 ); The second part of the "and" seems to be assigning "5" to $chapter but I'm normally used to the first part as being "If $chapter matches 5 then do something".

    Yes, your interpretation is correct. Perl's low-precedence boolean operators and/or/etc. can be used in place of if conditionals; the statement you showed is the equivalent of if ( $chapter =~ /5/ ) { $chapter = 5 } (and could arguably be written this way for better readability*). =~ is the binding operator in this case saying to match the regex on its right to the string on its left, and = is a simple assignment.

    * Yet another way to write that is using Statement Modifiers: $chapter = 5 if $chapter =~ /5/;, if you find that more readable. In fact, this is what Perl optimizes the above statement to:

    $ perl -MO=Deparse -e '( $chapter =~ /5/ ) and ( $chapter = 5 );' $chapter = 5 if $chapter =~ /5/;

    Note that I'd say the only difference between the statements being discussed here is readability, so since you had to ask about the syntax, you may prefer to rewrite the statement in one of the above forms if that helps you to be able to read the code more easily.

      In fact, this is what Perl optimizes the above statement to:

      You have it backwards. «and» isn't optimized into an «if». There isn't even an «if» opcode. An «if» statement is compiled into code that includes an «and» or an «or» opcode.

      $ perl -MO=Concise,-exec -e'f() and g()' 1 <0> enter v 2 <;> nextstate(main 1 -e:1) v:{ 3 <0> pushmark s 4 <#> gv[*f] s/EARLYCV 5 <1> entersub[t2] sKS/TARG 6 <|> and(other->7) vK/1 7 <0> pushmark s 8 <#> gv[*g] s/EARLYCV 9 <1> entersub[t4] vKS/TARG a <@> leave[1 ref] vKP/REFC -e syntax OK $ perl -MO=Concise,-exec -e'g() if f()' 1 <0> enter v 2 <;> nextstate(main 1 -e:1) v:{ 3 <0> pushmark s 4 <#> gv[*f] s/EARLYCV 5 <1> entersub[t4] sKS/TARG 6 <|> and(other->7) vK/1 7 <0> pushmark s 8 <#> gv[*g] s/EARLYCV 9 <1> entersub[t2] vKS/TARG a <@> leave[1 ref] vKP/REFC -e syntax OK $ perl -MO=Concise,-exec -e'if (f()) { g() }' 1 <0> enter v 2 <;> nextstate(main 1 -e:1) v:{ 3 <0> pushmark s 4 <#> gv[*f] s/EARLYCV 5 <1> entersub[t2] sKS/TARG 6 <|> and(other->7) vK/1 7 <0> pushmark s 8 <#> gv[*g] s/EARLYCV 9 <1> entersub[t4] vKS/TARG a <@> leave[1 ref] vKP/REFC -e syntax OK

      «f() and g()» and «g() if f()» generate exactly the same opcode tree, so Deparse outputs what it thinks is clearest.[1]

      If the whole conditional expression is negated, the «not» and the «and» opcodes will be optimized into an «or».

      $ perl -MO=Concise,-exec -e'if (!f()) { g() }' 1 <0> enter v 2 <;> nextstate(main 1 -e:1) v:{ 3 <0> pushmark s 4 <#> gv[*f] s/EARLYCV 5 <1> entersub[t2] sKS/TARG 6 <|> or(other->7) vK/1 7 <0> pushmark s 8 <#> gv[*g] s/EARLYCV 9 <1> entersub[t4] vKS/TARG a <@> leave[1 ref] vKP/REFC -e syntax OK

      1. «-exec» shows the code that's executed. And while the code executed for «if (f()) { g() }» is identical to the code executed for the other two, the opcode tree is different. There are vestiges in the opcode tree of which Deparse can take advantage to differentiate the «if (f()) { g() }» case from the others. Remove «,-exec» to see the tree.
      I quickly wrote a script to verify and sure enough! I learned something new here. Many thanks haukex for your help!

        Glad to help! And yes, writing quick test scripts is one of my personal favorite ways to learn things like this.

        Side note: I don't know what the original author's intentions were, but it may be worth considering that any $chapter containing 5, including 15, 50, etc. will all be silently converted to 5.

        I really enjoyed the posts from haukex - all great advice.

        There is another formulation of your "if this" then do "that" task. I see that chapter "numbers" could be a string like "11V". You also don't say, but I suspect that there are other statements like your chapter 5 statement for other chapters.

        Instead of this being chapter number 5 specific, you could have a regex something like this at the beginning of the loop which would apply to any chapter string, not just chapter 5:

        $chapter =~ s/\D+//g; # remove all non-digit characters # or perhaps to avoid the /g flag, (I wouldn't code it this way # because it is overly complex) however: $chapter =~ s/^(\D*)(\d+)(\D*)/$2/; # remove all optional non-digit s +tuff # before or after the digits # try the above with "XX546YYY", just "453ZZ" and "AAA123ZZZ77548" as # cases to probe the limits... what happens if it is not just "11VI"?
        In both of the statements above, the string "5" is unaffected, but if it has some extra stuff like "5VI", the "VI" is removed. I would put a statement like that the beginning of the loop because it handles all number possibilities.

        A subtle point is that a "number" in Perl could be represented by a "string" or an actual "numeric value". By and large, you do not have to worry about this because Perl will "do the right thing". Your statement says: if the string that represents "chapter" contains a "5", then set chapter to a numeric 5 (meaning binary 0000101 instead of whatever the string "5" or "657"codes to).

        Perl makes this "string representing a number" to "actual binary number" conversion for you automatically. So, this is fine:

        my $x = "3"; if ($x > 3){...}
        In order to do the numeric comparison, Perl will convert the string "3" to binary and compare that to 000011.

        my $x = "chapter 5"; print "chap 5 ok!" if $x == 5; # Throws Warning: Argument "chapter 5" isn't numeric in numeric eq (== +) $x =~ s/\D+//g; # eliminate all non 0-9 characters from string print "chap 5 ok!" if $x == 5; # chapter 5 is ok now! # The string got "fixed" to be completely numeric # Then when Perl made it into binary number to compare against 5, it w +orked!
        BTW, there is one non-numeric string which Perl will convert to numeric without a warning: "zero0 but true". That will equal numeric binary zero, but will test as "true" in logical expression. The modern way to do that is the string "0E0". The DBI can use this in some situations where it wants to say: 1)the command worked (true), but 2) there were no valid results returned, i.e. numeric zero. Pretty Cute!

        One place where the difference between string and numeric representations of a number can occur is with leading zeroes.

        $x = "00005"; print "$x\n"; #yields "00005" $x += 0; #adding zero forces numeric conversion print "$x\n"; #yields "5"
        In some situations, I need an integer binary value anyway to put into some DB and what I receive as textual description of that number may have a leading zero. Instead of using regex, I just add zero that number. bingo, $num +=0; $num now has a binary representation in Perl whether it did or not before and there will not be any leading zeroes if I print it or use it otherwise in a string context.