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

Why does this work
sub r{@_[0..$#_]=reverse@_}; @a=('a'..'f'); r @a; print @a;
but this not?
sub r{@_=reverse@_}; @a=('a'..'f'); r @a; print @a;

Replies are listed 'Best First'.
Re: aliasing
by duff (Parson) on Jan 12, 2004 at 20:45 UTC

    Because in the first instance, you're assigning to individual scalars and thus modifying the aliased elements in @_ and in the second case you're replacing @_ (and thus destroying the aliasness of @_) with some new values.

    Hopefully I'm making enough sense.

      Another way of putting it is that each element of @_ is aliased to a parameter, but @_ itself is not aliased. So assigning to an element (or all elements, as in this case) uses the alias but assigning to the array doesn't.
Re: aliasing
by BrowserUk (Patriarch) on Jan 12, 2004 at 21:25 UTC

    I think you got the answer to your question, but it might be worth pointing out that reverse already has smarts built in to reverse the elements of an array in-place if the source and destination are the same.

    # memory use 2368k my @r = 1...1000000; # memory use 62804k @r = reverse @r; # memory use 62812k

    As you can see, almost no extra memory is consumed by the process of the reversal. Contrast that with your aliased reverse in which an extra 16MB is consumed to reverse a million element array.

    sub r{ @_[ 0 .. $#_ ] = reverse @_; } # memory use 2368k my @r = 1 .. 1000000; # memory use 62812k; r @r; # memory use 78664k;

    Using reverse in the normal way is also markedly quicker.

    #! perl -slw use strict; use Benchmark qw[ cmpthese ]; sub r{ @_[ 0 .. $#_ ] = reverse @_ } our @n = 1 .. 1000; cmpthese( -3, { normal_reverse => q[ my @r = reverse @n; ], aliased_reverse => q[ my @r = r( @n ); ], }); __END__ P:\test>320771 Rate aliased_reverse normal_reverse aliased_reverse 1360/s -- -78% normal_reverse 6110/s 349% --

    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    Timing (and a little luck) are everything!

      While not negating your statements, I'd like to point out a third option, with Benchmarking. It's a middle of the road option.
      #!/usr/local/bin/perl use strict; use Benchmark qw[ cmpthese ]; sub r{ @_[ 0 .. $#_ ] = reverse @_ } sub s{ @{$_[0]} = reverse @{$_[0]} } our @n = 1 .. 1000; cmpthese( -3, { normal_reverse => q[ my @r = reverse @n; ], aliased_reverse => q[ my @r = &r( @n ); ], aliased_ref_reverse => q[ &s( \@n ); ], }); __END__ Rate aliased_reverse aliased_ref_reverse norm +al_reverse aliased_reverse 644/s -- -57% + -77% aliased_ref_reverse 1508/s 134% -- + -46% normal_reverse 2793/s 334% 85% + --

      ------
      We are the carpenters and bricklayers of the Information Age.

      Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

        That certainly stops the memory consumption. For simple reversal, I don't see any benefit, but if the sub is going to do other things as well, I agree that using pass-by-reference rather than value for subs that are designed to modify their arguments makes perfect sense.


        Examine what is said, not who speaks.
        "Efficiency is intelligent laziness." -David Dunham
        "Think for yourself!" - Abigail
        Timing (and a little luck) are everything!

      I can't comment on your memory statistics, but I definitely see a list-context rv2av or padav for reverse @r, so each element of the array does appear to be put onto the stack.
      $ perl -MO=Concise -we'@r = reverse @r' c <@> leave[1 ref] vKP/REFC ->(end) 1 <0> enter ->2 2 <;> nextstate(main 1 -e:1) v ->3 b <2> aassign[t6] vKS/COMMON ->c - <1> ex-list lK ->8 3 <0> pushmark s ->4 7 <@> reverse[t5] lK/1 ->8 4 <0> pushmark s ->5 6 <1> rv2av[t4] lK/1 ->7 5 <#> gv[*r] s ->6 - <1> ex-list lK ->b 8 <0> pushmark s ->9 a <1> rv2av[t2] lKRM*/1 ->b 9 <#> gv[*r] s ->a -e syntax OK $ perl -MO=Concise -we'my @r; @r = reverse @r' c <@> leave[1 ref] vKP/REFC ->(end) 1 <0> enter ->2 2 <;> nextstate(main 1 -e:1) v ->3 3 <0> padav[@r:1,2] vM/LVINTRO ->4 4 <;> nextstate(main 2 -e:1) v ->5 b <2> aassign[t3] vKS/COMMON ->c - <1> ex-list lK ->9 5 <0> pushmark s ->6 8 <@> reverse[t2] lK/1 ->9 6 <0> pushmark s ->7 7 <0> padav[@r:1,2] l ->8 - <1> ex-list lK ->b 9 <0> pushmark s ->a a <0> padav[@r:1,2] lRM* ->b -e syntax OK

        I have to reverse (sic:) your statement. I can't comment on your opcode dumps -- I don't understand them enough.

        However, it should be fairly simple to test my assertions on the memory consumption.

        I used win32's task manager, but if you inserted a few <>; statements in place of the comments in the snippets I posted, you should be able to use top in another console to watch the memory usage and growth.


        Examine what is said, not who speaks.
        "Efficiency is intelligent laziness." -David Dunham
        "Think for yourself!" - Abigail
        Timing (and a little luck) are everything!

Re: aliasing
by hardburn (Abbot) on Jan 12, 2004 at 21:01 UTC

    To add to what others already mentioned--according to perldata, these two statements are exactly equivalent:

    @a[0,2,4] = (0 .. 2); ($a[0], $a[2], $a[4]) = (0 .. 2);

    So the slice is modifying the elements directly, but the assignment to a plain array clobbers that array.

    Update: Spelling error fixed. Thanks cLive ;-).

    ----
    I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
    -- Schemer

    : () { :|:& };:

    Note: All code is untested, unless otherwise stated