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

I've managed to reduce my confusion to a very simple example:

sub two_hard { @_ = @_[0,1]; @_ } sub two_easy { @_[0,1] } print "two easy: " . two_easy( 'one', 'two' ) . "\n"; print "two hard: " . two_hard( 'one', 'two' ) . "\n";

I think these two should be equivalent. They're both returning the same thing, but one of them does it without assigning it to something else first. (This question comes from trying to optimize some code that is like two_hard into something like two_easy.) However, in spite of their obvious similarity, the output of the above is:

two easy: two two hard: 2

Why does this happen?

Replies are listed 'Best First'.
Re: Why does assignment change the result?
by jdporter (Paladin) on May 23, 2007 at 18:48 UTC

    I think what demerphq said could use some clarification.

    You are calling the two subs in scalar context, due to the fact that the results are being immediately subjected to the dot operator (string concatenation). So you need to understand what the return expression (or, lacking that, the last expression evaluated) in each sub will do when evaluated in scalar context.

    @_ in scalar context results in the length of the array, which in this case is the integer 2. It doesn't even matter what the contents of the array are.

    @_[0,1] in scalar context results in the last value in that list. This is because @_[0,1] is a list expression, not an array. And all lists, or listy things, evaluate to their last value when in scalar context. In this case, that value is the string 'two'.

    You should try altering your little program to call those subs in list context, to see what happens. (It's probably what you expected in the first place.) The easiest and best way to do this is to replace those dots with commas, like so:

    print "two easy: ", two_easy( 'one', 'two' ), "\n"; print "two hard: ", two_hard( 'one', 'two' ), "\n";
    Remember that print takes an unlimited list of arguments, and they're all evaluated in list context. (But the dot operator has higher precedence than the comma.)

    A word spoken in Mind will reach its own level, in the objective world, by its own weight
Re: Why does assignment change the result?
by demerphq (Chancellor) on May 23, 2007 at 18:21 UTC

    If you pass in more than two args or change the assignment from being what is essentially a no-op (such as passing in a tied value) youll see the two are quite different. One returns an array and the other a list. A list in scalar context is its rightmost element, an array in scalar context is its size. Additionally @_ is special, assigning to it alters the original variables passed in. Im actualy surprised you dont get an attempt to modify a read only value error from two_hard().

    ---
    $world=~s/war/peace/g

      Additionally @_ is special, assigning to it alters the original variables passed in.

      No, it doesn't. Assignment to elements of @_ will change those variables; assignment to @_ replaces its contents entirely. Isn't Perl wonderful? :-)

      Update: IOW, what suaveant said.

      I am not positive but I think by assigning to @_ wholesale he is replacing the aliases, not writing to them.

                      - Ant
                      - Some of my best work - (1 2 3)

        whoops. good catch. still anything that assigns to @_ is a code smell to me.

        ---
        $world=~s/war/peace/g

      A list in scalar context is its rightmost element, an array in scalar context is its size.

      You're right of course. But then you got me thinking... why does the ()= trick work? It is supposed to put its rhs in list context and then is generally put back in scalar context to get the length of a generated list... Is it special cased?

      print scalar(()=eval { wantarray ? qw/foo bar baz/ : 1 });

      (This prints 3 of course.)

        I'm fairly sure it's not special cased. It's essentially what you said. (Look at the associativity see how the context propagates and look at the precedence to see who wins :).

        I'm baffled by this too. How can it print 3 when there never is an array? Or is there? (I only see an empty list assigned to, which is not an array, right?)

      If I pass in three arguments, I get the same output.

      print "two easy: " . two_easy( 'one', 'two', 'three' ) . "\n"; print "two hard: " . two_hard( 'one', 'two', 'three' ) . "\n";

      I don't understand what you mean about passing in a tied value.

        To paraphrase what demerphq said :

        • Your "two_hard" sub returns an array (@_).
        • Your "print" statement uses the concat "." operator, which makes the usage of the return value in a scalar context
        • In a scalar context, an array evaluates to the number of elements (2)
        Similarly, your "two_easy" sub returns a LIST. (Because you picked out elements of the array, using an array slice).

        In a scalar context, a list evaluates to the last element.

        For now, you can ignore the "tied" comment.

        Update: jdporter is apparently a faster typist than I am. His post essentially says the same thing.

             "An undefined problem has an infinite number of solutions." - Robert A. Humphrey         "If you're not part of the solution, you're part of the precipitate." - Henry J. Tillman

Re: Why does assignment change the result?
by Anonymous Monk on May 24, 2007 at 03:15 UTC

    This is one of the biggest issue with Perl. @_ is a reference, and change its value will be seen in the calling func.

    Any one didn't catch that at the very beginning is not a true saint.

          "@_ is a reference, and change its value will be seen in the calling func"

      Er - NO. From perldoc perlsub

      The array @_ is a local array, but its elements are aliases for the actual scalar parameters.

      So, you are wrong on 2 counts:

      • @_ is an array, not a reference
      • Since it is local, changing @_ will NOT change the caller
      Perhaps what you meant to say was - what trips perl newbies is that fact that the ELEMENTS of @_ are aliased to the caller's parameters - hence changing an ELEMENT of @_ would modify the caller's variable.

           "An undefined problem has an infinite number of solutions." - Robert A. Humphrey         "If you're not part of the solution, you're part of the precipitate." - Henry J. Tillman

        I wanted to understand what was happening, what affected the caller and what didn't so I put a short script together. I may have missed ways to get at @_ and I would be interested if any omissions were pointed out.

        The output produced is

        At initialisation Contents are 1 2 In assignTo - assign to @_ Contents now 1 2 In listArgs - list of lexicals Contents now 1 2 In loopOver - loop over @_ Contents now 2 3 In refTo - take reference to @_ elements Contents now 3 4 In shiftArgs - lexicals via shift Contents now 3 4 In useDirect - increment elements directly Contents now 4 5

        A very interesting topic that has cleared up some misunderstandings.

        Cheers,

        JohnGG