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

My fellow monks,

I have witnessed the following in our codebase at $work:

sub new { $_[0]->{class} = shift; validate( @_, ... ); my $self = {}; return bless $self, __PACKAGE__; }

(As an aside, this is the same programmer who inspired A class that cannot be subclassed.)

To my dismay, this actually works. The effect is:

What's interesting to me about this is that the shift happens before perl determines what $_[0] is for the assignment. If it didn't, it would always die because the class name, a string, wouldn't be allowed as a hash reference. My question is, is this reliable? Can we safely assume that this will not change in some future version of Perl? More to the point, can I reject this construct in a code review on the grounds that it can't be trusted to continue to do what it does now?

When I pasted this snippet into the CB, there was some question about why one would want to do this. As it turns out, the reason is "validation". The contents of @_ get passed to to something based on Params::Validate, which then ensures that the 'class' is what it ought to be.

If the hash ref in question were later blessed into the given class, I could see wanting to remember the original class in case the object is reblessed into something else. That's not what's going on in this case, however.

I thank you for your insights.

Replies are listed 'Best First'.
Re: Will "$_[0]=shift" always resolve shift first?
by ikegami (Patriarch) on Sep 08, 2008 at 21:44 UTC

    Nowhere does Perl guarantee (or even document) the order in which operands are evaluated. For example, consider

    var() = foo() + bar() * baz();

    Perl is documented to multiply the result of bar() with the result of baz() before adding the product to the result of foo(), but it doesn't document when it calls the functions to get the results.

    Can you guess the order?

    I was going to say Perl always evaluates operands in left to right order, but it's not the case.

    var() = foo() + bar() * baz(); 6 1 2 3 7 5 4

    You can see that here:

    my $x; sub var : lvalue { print 'var'; $x } sub foo { print 'foo'; 3 } sub bar { print 'bar'; 4 } sub baz { print 'baz'; 5 } local $\ = "\n"; var() = foo() + bar() * baz(); print($x);
    >perl 709887.pl foo bar baz var 23

    However, the order in which Perl evaluates operands is not platform- or compiler-specific. And some people rely on the order for some operators, particularly the comma operator. So it's not likely to change.

    There's a catch where lvalues are involved. Even if you know the order in which operands are evaluated, you could be surprised by the results. The operands of the comma operator are evaluated left to right, but you'd think otherwise by looking at the following:

    >perl -le"$i=3; print 0+$i,$i,++$i,$i" 3444

    In short, you might be able to rely on the order in which operands are evaluated, but I consider it a bad practice. Avoid changing an argument and reading it in the same expression. At the very least, it reduces readability and maintainability. The length of this post is proof of that.

      I was going to say Perl always evaluates operands in left to right order, but it's not the case.

      It does, unless precedence or associativity gets in your way. Your example demonstrates this quite well:

      var() = foo() + bar() * baz(); 6 1 2 3 7 5 4 Precedence tightest: * + loosest: =

      So the arguments are evaluated left-to-right on the LHS of the =, then the sub expressions in order of their precedence, then the RHS. (perltrap documents that the RHS is evaluated first, btw).

      Update: the more I think about this, the less sure I am. So try not rely too much on it ;-)

      In short, you might be able to rely on the order in which operands are evaluated, but I consider it a bad practice

      Fully agreed.

      Second update

      Ok, the operands are evaluated left-to-right, the operators according to their precedence/associativity.

      The assignment operator is an exception (on which we rely quite often).

        It does, unless precedence or associativity gets in your way. Your example demonstrates this quite well:

        Not at all. My example shows that precedence has no effect on the operatoroperand evaluation order.

        And since associativity breaks ties in precedence, how could it possibly have any effect on the operatoroperand evaluation order if precedence doesn't.

        Updated (18:55 EDT): I keep typing "operator" when I try to type "operand"! argh!

      Changing an argument and reading it in the same expression is Just Fine. Consider $x = $x + 1, or $number = next_in_sequence($number).

      Better advice would be "avoid stunt code".

Re: Will "$_[0]=shift" always resolve shift first?
by moritz (Cardinal) on Sep 08, 2008 at 21:24 UTC
    is this reliable?

    Yes.

    There's quit some code on CPAN that relies on the relative order of execution, und such things aren't changed without a very good reason. The ports want to use CPAN, not break it.

      Above, moritz says:

      und such things aren't changed without a very good reason

      Elsewhere in this thread, moritz offers the following aside:

      (perltrap documents that the RHS is evaluated first, btw).

      And let us see what perltrap actually says

      LHS is evaluated first in perl4, second in perl5; this can affect the relationship between side-effects in sub-expressions.

      So it appears there must have been a very good reason found when Perl5 was produced?

      In any case, we now have documentation of the evaluation order of the sides of assignment expressions. This will cause many to feel more justified in relying on this order. Of course, the documentation is actually documentation of this order having already changed (with no motivation mentioned and I'd bet money on the motivation being "it was convenient to implement that way" because that is how little justification is needed for one to change the order of evaluation of sub-expressions and exactly why it is unwise to rely on such things). So I find this documentation to be justification for not relying on the "current" order of evaluation (and I suspect that part of the point of Perl 6 will mean that this order is likely to be less "constant" in Perl 6).

      To be fair to moritz, the above node was posted before the one containing the aside (which also appears to backpedal significantly on the above simple, bold proclamation). But I felt the above simple, bold proclamation deserved a direct reply (including a link to the backpedaling).

      Also, the claim that documented behavior of local($foo)= $foo; and my $bar= $bar; requires the "current" order of evalatution is quite bogus. Indeed, that behavior for local didn't change between Perl4 and Perl5 but the order of evaluation did, QED.

      You can always evaluate the RHS and push the resulting value(s) onto the stack before you do anything with the LHS or after you've done everything to the LHS except for actually assigning the computed values over. The main determination as to what order you do those in is which way is more convenient for those results to appear on the stack.

      Now, if Perl6 makes the mistake (IMHO) of allowing context (of the RHS of an assignment statement) to be ambiguous at compile time, then this would finally force the implementors' hands on this order of evaluation issue. It would also require changing away from the "current" Perl5 order of evaluation. More likely, being able to optimize (one of the core motivations for Perl6) almost always also means being able to change a lot of order of evaluation "decisions" pretty much willy-nilly. That is, some subtle change to code not even obviously connected to the assignment statement could change the order of evaluation. In Perl 6, there should even be the possibility of both sides being evaluated "at the same time" or interleaved.

      - tye        

        So it appears there must have been a very good reason found when Perl5 was produced?

        I didn't think of major releases when I wrote that. If you sacrifice backwards compatibility anyway, (and I believe that the introduction of perl 5 did, but I'm too young to have been hacking at that time), any valid reason is good enough IMHO.

        Also, the claim that documented behavior of local($foo)= $foo; and my $bar= $bar; requires the "current" order of evalatution is quite bogus. Indeed, that behavior for local didn't change between Perl4 and Perl5 but the order of evaluation did, QED.
        Indeed, in these cases it's not really the order of evaluation that matters, but the scope. The scope of a my variable starts at the end of the statement in which it is declared, which you can easily test like this:
        $ perl -Mstrict -e 'my $x = $x' Global symbol "$x" requires explicit package name at -e line 1.
        Now, if Perl6 makes the mistake (IMHO) of allowing context (of the RHS of an assignment statement) to be ambiguous at compile time

        In Perl 6 it is known at parse time if an assignment is in list context of item context, if that is what you mean. And it has to be, because the list assignment infix:<=> has a different precedence than the item assignment infix:<=> operator, which allows for the magic of my @x = 1, 2, 3; and $x = 1, $y = 2 both to do what you mean.

Re: Will "$_[0]=shift" always resolve shift first? (meh)
by tye (Sage) on Sep 09, 2008 at 02:46 UTC

    Some will argue that it is unlikely to change. Many invalid arguments will be given that claim to explain why the order has to be the way that it is. Nothing new there.

    Do you really care? I don't.

    I don't consider it reasonable to rely on the order of evaluation of expressions that aren't separated by a semicolon (or other statement boundary), a short-circuiting logical operator (&&, ||, etc.), or (sometimes) a comma. And the case of comma is very often one to avoid.

    I don't want to waste time trying to convince myself that some dubious reliance on a particular order of evaluation is reasonable or not; it is usually much faster to just remove the dependence and make the code clearer (thus saving even more time in future).

    - tye        

Re: Will "$_[0]=shift" always resolve shift first?
by betterworld (Curate) on Sep 08, 2008 at 23:41 UTC

    I've found the following code in Perl 5.10.0's testsuite in t/op/local.t:

    @a = ('a', 'b', 'c'); { local($a[1]) = 'foo'; local($a[2]) = $a[2]; is($a[1], 'foo'); is($a[2], 'c'); undef @a; }

    Now, I daresay (and I hope that my head is clearer than when I wrote my last node) that this only works if "=" evaluates its right-hand operand first.

    The important line is the one with the second local. As a run-time operator, local() effectively sets its operand ($a[2] in this case) to an undefined value. But then this new value will be set to the value of $a[2] before local was executed. This would not work the other way round.

    Apart from this extract, local.t contains more similar tests.

    In a nutshell: Even though I could not find any hints in the documentation, the test suite suggests that kyle's coworker's code is reliable.

      Even though I could not find any hints in the documentation,

      moritz pointed out that perltrap documents the operand evaluation order for assignment operators.

Re: Will "$_[0]=shift" always resolve shift first?
by betterworld (Curate) on Sep 08, 2008 at 21:44 UTC

    As you can see from the listing in perlop, the assignment operator ("=") has right associativity, which means that the right side is evaluated before the left side.

    Incidentally, this is the very reason that the following common idiom works as you expect:

    $x = $y = 42;

    Had "=" its left side evaluated first, the 42 would not make it into the $y variable, which would lead to code breakage all over the planet ;)

    Update: Hm, scratch that, I think I confused a few terms. The associativity tells us how the statement gets parsed, not how it gets executed.

      Associativity controls the order in which operators are evaluated in a chain of operators of the same precedence.

      If operand evaluation order was tied to operator precedence and associativity, bar() would be evaluated before foo() in

      foo() + bar() * baz()

      (It's not.)

      So what would you expect the following to print?

      use strict; my @array = (0, 0, 0); my $x = 0; $array[++$x] = $x++; print "@array\n"; $x = 0; $array[++$x] = $x; print "@array\n"; $x = 0; $array[++$x] = ++$x; print "@array\n";
      <prints>
      0 0 0 0 1 0 0 1 2

      with Perl 5.8.7.


      Perl reduces RSI - it saves typing

        What is your point?

        If your point was to contradict betterworld, it doesn't. While it may not *appear* to be the case, the RHS of the assignment is evaluated before the LHS in every instance.

        If your point is that it can get very complex very fast, I agree.

Re: Will "$_[0]=shift" always resolve shift first?
by SuicideJunkie (Vicar) on Sep 09, 2008 at 17:30 UTC

    I believe that the whole question about reliance on evaluation order is a distraction from the real issue...

    Whether it is reliable or not will not change the fact that it is opaque:

    To my dismay, this actually works. The effect is: ...
    Note that your external explanation is 20x the size of the program code + comments. And how much time did it take you to determine that much?
    That is just not right

    Therefore, regardless of whether it works right now, it should be marked for rewriting and clarification. At the very least, comment your analysis above into the code so the next person to see that doesn't suffer the same.

    PS: Compression ratio might be a good analysis to try: (Size of Code+Comments) vs (size of additional documentation needed to understand the code)

    A higher ratio implies the code is clear, with a ratio of infinity being code that is completely self-explanatory.