Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked
 
PerlMonks  

$1 not "freezing" in an addition

by grondilu (Friar)
on Dec 14, 2012 at 12:17 UTC ( [id://1008823]=perlquestion: print w/replies, xml ) Need Help??

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

Hello monks,

I faced a weird failure while writing a solution to this rosalind problem. I won't paste the whole solution but here is the relevant part:

sub distance { ... do { return $1 + distance($_) if s/\(#:(\d+)\)/#/ } until ... ... }

This code returned a wrong result and a "use of uninitialized value $1 in addition" warning message. Such a warning is weird since I am in a section code where the substitution regex is supposed to have happened, thus affecting $1.

To fix the code, I had to write 0+$1 instead of just $1. (my $x = $1) would have worked too. The warning message disappeared and I ended up with the correct answer. As I understand it, it seems that I had to somehow "nail" the value before using it. But I don't quite understand why.

Any idea?

PS. Here is a simple example suggested below that reproduces the failure (not the warning, though):

sub butter { my $_ = shift; return $1 + butter($_) if s/(\d+)//; return 0; } print butter 'effect / 7ACV06';

It prints 12 with $1, and 13 with 0+$1.

Replies are listed 'Best First'.
Re: $1 not "freezing" in an addition
by tobyink (Canon) on Dec 14, 2012 at 12:32 UTC
    I've struck out some text below which has become irrelevant as the question has been updated...

    What version of Perl? The following seems to work OK on Perl 5.8.9, 5.10.1 and 5.16.0...

    Update: you've posted the whole code now. I get the same error. Another solution (other than 0+$1) is to quote the variable, "$1".

    This is indeed strange, but it's not the first weird bug involving magic variables, and probably won't be the last. Best to report it to the p5p bug tracker I think.

    Update #2: I found a similar problem a few months ago... Bizarro %+ hash slice bug in Perl 5.10 to 5.14..

    Update #3: Now you've added some simplified code and I can see what's going on! That code actually runs more or less how I'd expect it to run.

    The problem is that you're expecting that on the first iteration, $1 will be evaluated before the recursive call to butter however that is not necessarily the case. With the exception of the short-circuiting operators (&&, || and so on), Perl does not guarantee what order the two operands of a binary operator will be evaluated in.

    For example:

    use strict; use warnings FATAL => 'all'; sub foo { print "foo just happened\n"; 40; } sub bar { print "bar just happened\n"; 2; } print foo + bar, "\n";

    What happens? You might expect that you'd get output like this:

    foo just happened bar just happened 42

    The "correct" behaviour here is undefined. As it happens, on my machine the first two lines are reversed.

    So your problem is that the recursive call to butter is evaluated before $1. The second call of butter ends up setting $1 to 6, overwriting the existing value 7. Blammo! 6+6 is 12.

    Quotes around $1 or using a subexpression like (0+$1) coerces Perl into changing the evaluation order, and (by luck more than anything else) you get the correct answer.

    Update 4: the C FAQ (C is a programming language with similar undefined behaviour here) has this related entry.

    Update 5: this is very fragile stuff. If you add a prototype of () to the foo function in my example, then it changes the output order (at least it does here). If take away the prototype but call foo with parentheses, like foo() then that has the same effect.

    Interesting SoPW post though. Kept me entertained. Much thanks.

    perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'

      Here's another fun example...

      use 5.010; my $z = 0; sub xxx () { $z = 1; return 2; } sub yyy () { my $zz = $z; $z = 0; return 2 + $zz; } if (xxx + yyy == yyy + xxx) { say "Addition is commutative"; } else { say "The world has gone haywire!"; }

      See addition; commutativity; etc.

      Don't write functions with side-effects. And if you really must write functions with side-effects, be careful using them in expressions.

      perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'

      What happens? You might expect that you'd get output like this:

      The example you gave has nothing to do with operand evaluation order and everything to do with operator precedence. Your code is equivalent to print(foo(+bar(), "\n"));, for which operand evaluation order *is* defined.

      If you had used print foo() + bar(), "\n"; (or added the empty prototype, as you mentioned), then you would be relying on undefined operand evaluation order, yet it will give you the following on all machines:

      foo just happened bar just happened 42

      Perl does not guarantee what order the two operands of a binary operator will be evaluated in.

      Operand evaluation order is documented and thus guaranteed for about half the ops*. And while it's not guaranteed for the other ops, it never changes (left-to-right for all ops (including **) except assignment ops).


      * — Going from memory, operand evaluation order is documented for:

      • && || // and or xor ?:
      • = **= += *= &= <<= &&= -= /= |= >>= ||= .= %= ^= //= x=
      • , => (in scalar context)
      • .. ... (in scalar context)

      So your problem is that the recursive call to butter is evaluated before $1.

      Quotes around $1 or using a subexpression like (0+$1) coerces Perl into changing the evaluation order,

      Both of those statements are completely false. In all existing builds of Perl, the LHS of addition is always evaluated before it's RHS, so $1 is evaluated first in $1+f(). Keep in mind that $1 evaluates to a scalar, not a string. It's "+" that extracts the string from it, by which point the scalar's value has changed.

      Study the following:

      $_ = 3; print sub :lvalue { print "$_+"; $_ }->() + sub :lvalue { print "10="; ++$_; 10 }->(), "\n";
      3+10=14

      You can clearly see $_ being evaluated before 10 even though the value to which $_ is set by the RHS is used by the addition.

      (Sorry for making a chain of post. I initially assumed your post was correct and that it just had a small problem, so I commented on it before reading on. And then the same thing happened another time. Only after that did I realize the entire post is wrong.)

      Very instructive. Thanks.

Re: $1 not "freezing" in an addition
by CountZero (Bishop) on Dec 14, 2012 at 17:13 UTC
    Without having tested this, could it be that $1 being a global variable, is continously overwritten in each next iteration of this recursive subroutine, until the last step where $1 becomes undef due to the regex failing? That undef value then "trickles" back up the recursive subs and gives this error.

    By "interpolating"/"quoting" the $1 perhaps the acctual value of $1 at that stage in the recursion gets indeed saved somehow.

    It is always a good idea to immediately save those special variables (such as $_, @_, $1, ...) into a lexical variable to avoid these pitfalls. We tend to forget that these are global variables that are therefore prone to "action at a distance".

    CountZero

    A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity." - The Tao of Programming, 4.1 - Geoffrey James

    My blog: Imperial Deltronics
      I think the logic is the same as using Foo($1,$2) versus Foo("$1","$2"), it seems its a good habit to quote $1,$2 when you're not doing assignment

        it seems its a good habit The safest thing you can do is to stringify $1,$2 when you're not doing assignment using them in an lvalue context.

        f("$1") for ("$1") \"$1"
Re: $1 not "freezing" in an addition
by bart (Canon) on Dec 14, 2012 at 12:29 UTC
    Does it work better if you "unreverse" the if condition?
    if(s/\(#:(\d+)\)/#/) { return $1 + distance($_); }

    As I don't have the whole code, it's a bit difficult to test. And it's just a hunch.

      Nope. (It was actually initially written as such, anyway)

Re: $1 not "freezing" in an addition
by Anonymous Monk on Dec 14, 2012 at 12:27 UTC

    Prove it :)

    $ perl -Mstrict -Mwarnings -l junk 13 $ cat junk sub butter { local $_ = 'effect / 7ACV06'; return $1 + junk($_) if s/(\d+)//; } sub junk { local $_ = $_[0]; return $1 if s/(\d+)//; } print butter(); $

      ok I added the whole code

      PS. Modifying your code, here is a simple version that reproduces the error:

      sub butter { my $_ = shift; return $1 + butter($_) if s/(\d+)//; return 0; } print butter 'effect / 7ACV06';
Re: $1 not "freezing" in an addition (about rosalind)
by Anonymous Monk on Dec 24, 2012 at 04:31 UTC

    This is a question about rosalind, specifically the notation of the question, is there a guide to the notation? What is \ldots ?

        If you turn on javascript you will see the end results instead of the raw latex

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1008823]
Approved by Ratazong
Front-paged by bart
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others romping around the Monastery: (4)
As of 2024-03-28 14:38 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found