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

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

I am starting to learn Perl by reading "Learning Perl" and testing on a Centos box.

I typed in a piece of code from the book and it did exactly what the book said it should do... But I don't know why!

This is the code I typed in (modded to my coding requirements):

use strict; use warnings; my @lArray; my @lNums = 1..3; unshift @lArray, 5; print "Array: @lArray\n"; unshift @lArray, 4; print "Array: @lArray\n"; unshift @lArray, @lNums; print "Array: @lArray\n";

The output of the third print is "Array: 1 2 3 4 5", exactly as the book showed. My question is why is it that way? After thinking about it my expectation was that it would be "Array: 3 2 1 4 5".

I expected "unshift @lArray, @lNums" to expand to "unshift @lArray, 1 2 3" and then be processed as though:

unshift @lArray, 1 unshift @lArray, 2 unshift @lArray, 3

However, it doesn't. It looks like the unshift operation is processing the values given in reverse order (3 2 1). Is this true? If not, how is it putting them in in the given order rather than reversed?

Anyway, that's my problem; totally confused by Chapter 3.

Replies are listed 'Best First'.
Re: I am confused by a "Learning Perl" sample showing "unshift"
by haukex (Archbishop) on Jan 22, 2021 at 20:43 UTC
    My question is why is it that way?

    See unshift:

    Note the LIST is prepended whole, not one element at a time, so the prepended elements stay in the same order. Use reverse to do the reverse.
Re: I am confused by a "Learning Perl" sample showing "unshift"
by LanX (Saint) on Jan 22, 2021 at 21:07 UTC
    unshift is like push just from the lower side, kind of inverted shift

    a little perldebugger demo

    DB<9> x @a =(5) 0 5 DB<10> unshift @a,4 DB<11> x @a 0 4 1 5 DB<12> unshift @a,1,2,3 DB<13> x @a 0 1 1 2 2 3 3 4 4 5 DB<14> push @a, 6 DB<15> x @a 0 1 1 2 2 3 3 4 4 5 5 6 DB<16> p shift @a 1 DB<17> x @a 0 2 1 3 2 4 3 5 4 6 DB<18> p pop @a 6 DB<19> x @a 0 2 1 3 2 4 3 5 DB<20>

    I recommend trying perl -de0 to play around by yourself

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

      Thanks for the response. My problem is not really what it's doing as that's obvious by the results. My problem is more the how.

      unshift when acting with one value is (sort of) moving everything up one slot, then filling in the new [0] slot.

      However, when acting with multiple values, is it:

      reversing the order of the incoming values then (move everything up one slot, fill in the new [0] slot) N times

      or

      count the incoming values, move everything up N slots, then fill in the N slots [0],[1],[2],... with the incoming values

      My personal bet is on the second as it'd be faster. Admitted, this is not one of the all-time consuming questions about the Universe, but when something doesn't act the way I expect, I like to know how it achieves the result(s) that it gets. Or should I simply go with "No, I won't pay any attention to the man behind the curtain."?

      And yes, thank you, I'll be fiddling with the debugger sooner or later.

        For reasons of symmetry this

        unshift @a, @b means @a = ( @b, @a )

        Much the same way as

        push @a, @b means @a = ( @a, @b )

        So no implicit reversing of @b.

        HTH :)

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery

        Or should I simply go with "No, I won't pay any attention to the man behind the curtain."?

        Yes. Perl functions generally do what they say on the tin :-)

        Admitted, this is not one of the all-time consuming questions about the Universe, but when something doesn't act the way I expect, I like to know how it achieves the result(s) that it gets.

        As far as I can tell from the internal implementation of pp_unshift and av_unshift, your guess that it's the latter (Edit 2: that is, shift the array up by N at once) appears to be correct. But as stated above, that should be considered an implementation detail that you don't need to worry about.

        My personal bet is on the second as it'd be faster.

        If concerned about performance, always measure first, using Benchmark and/or Devel::NYTProf.

        Update: Actually, LanX's post here provides something more interesting to benchmark. I've also added a built-in test of the benchmarked code here.

        use warnings; use strict; use Benchmark qw/cmpthese/; use constant TEST => 0; my $EXP = join $", 100..150, 1..50; cmpthese(-2, { unshift => sub { my @array = 1..50; my @add = 100..150; unshift @array, @add; "@array" eq $EXP or die "@array" if TEST; }, loop => sub { my @array = 1..50; my @add = 100..150; unshift @array, $_ for reverse @add; "@array" eq $EXP or die "@array" if TEST; }, concat => sub { my @array = 1..50; my @add = 100..150; @array = ( @add, @array ); "@array" eq $EXP or die "@array" if TEST; }, }); __END__ Rate loop concat unshift loop 273062/s -- -19% -33% concat 337646/s 24% -- -18% unshift 409595/s 50% 21% --
Re: I am confused by a "Learning Perl" sample showing "unshift"
by haukex (Archbishop) on Jan 22, 2021 at 22:23 UTC
    I expected "unshift @lArray, @lNums" to expand to "unshift @lArray, 1 2 3" and then be processed as though

    The first part is correct (the contents of the @lNums array becomes part of the argument list to unshift), and as for the second part, note it's very simple to express what you thought would happen using Statement Modifiers: unshift @lArray, $_ for @lNums;

Re: I am confused by a "Learning Perl" sample showing "unshift"
by talexb (Chancellor) on Jan 23, 2021 at 15:16 UTC

    I'm just going to link to this node of mine that I think explains how all of these operators work.

    In reality, I never use shift or unshift; I use push lots, and pop only occasionally. The only time I see shift used is at the top of a sub (my $foo = shift), and that's a code pattern that I dislike. I much prefer the pattern

    my ( $this, $that, $other ) = @_;
    over three successive shift statements. Just about any time you're doing a cut and paste in your code, alarm bells should be going off that ask -- "Is this really the correct thing to be doing?"

    Alex / talexb / Toronto

    Thanks PJ. We owe you so much. Groklaw -- RIP -- 2003 to 2013.

Re: I am confused by a "Learning Perl" sample showing "unshift"
by rsFalse (Chaplain) on Jan 23, 2021 at 22:13 UTC
    This natural confusion may occur because there are two ways how to prepend one array to another. You can prepend starting from the beginning of an array or from the end.
    Here, in Perl, unshift prepends an array starting from the end.
    Documentation says it is opposite to 'push', but how opposite? By how many of attributes - by one or two? One attribute is the positions of the target array to which new array is being added (to the beginning or to the end). Second is an orientation of second array. In Perl - by two.
Re: I am confused by a "Learning Perl" sample showing "unshift"
by harangzsolt33 (Chaplain) on Jan 24, 2021 at 20:03 UTC
    This shouldn't be confusing at all. The unshift() function in JavaScript, Perl's sister language, works the same way. When you want to unshift more than one item at a time, it grabs them as one and inserts them in front of the array in the same order as you put them. This is useful, because if we consider a real-life scenario where you want to pack some data, and the First name must come first, followed by Last Name and then the middle name, you could do something like : unshift(@PACKAGE, @NAMES); then the address, where the first element of address holds the house number, followed by street, city, state, zip code, in that order, the unshift method will not mess up the order. It will simply insert these items into the array just the way they appear in the other array: unshift(@PACKAGE, @ADDRESS); unshift(@PACKAGE, @DELIVERY_DATE_SELECTED); So, let's say you have a DATE array which contains the year first, followed by month and day. If you unshift that, the order will remain the same in the array, which is logical and that's how it should work.