Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

Re: shift vs @_

by sgifford (Prior)
on Oct 02, 2006 at 19:18 UTC ( [id://575930]=note: print w/replies, xml ) Need Help??


in reply to shift vs @_

As others have said, mostly this is a matter of personal style. Sometimes it can make a difference, though, since shift actually modifies @_.

For example, this code could allow a function to act as a regular sub, or as a method (assuming the first argument will never be a reference unless it's called as a method):

my $self = ref $_[0] ? shift : undef; my($arg1,$arg2,$arg3)=@_;

Similarly, if you have a sub that takes a list as its final argument, shifting off the non-list arguments can make it clearer what you're doing, especially if you pass the list on to any other subs:

sub my_sort { my $sortname = shift; return sort $SORTS{$sortname} @_; }

This can also be useful if you decide to goto another sub, since that sub will see the modified @_ as its argument list.

Finally, as a matter of style, I usually get $self or $class with shift in methods and constructors, since it makes it clearer what the explicit arguments are, as compared to the implicit object reference or class name passed in as the first argument.

Update: Corrections and clarifications from jimt.

Replies are listed 'Best First'.
Re^2: shift vs @_
by jimt (Chaplain) on Oct 02, 2006 at 19:54 UTC
    my $self = shift if (ref $_[0]); my($arg1,$arg2,$arg3)=@_;

    Eeek! This sort of thing can rapidly get you into serious trouble. First of all, there's the fact that this won't operate properly if it's called as a class method such as Some::Package->foo( qw(arg1 arg2 arg3) ); $_[0] is a string, in that case.

    Secondly, it breaks down if you pass in a normal reference as your first argument. foo({'hashkey' => 'hashval'}, qw(arg4 arg5)); You end up setting a normal arrayref as $self.

    And thirdly, and potentially most importantly, if the conditional fails, then the variable maintains its scope. There are other threads on the board talking about the pitfalls of constructs like my $xyz if 0, which is what can happen here. The short answer is in this case $xyz would end up acting like a static variable. The first time, it gets initialized to something random (well, not random - it's 0 (or undef?), but that's not guaranteed and could change at any time (not that it has) ), and on subsequent calls, it maintains its value.

    It doesn't directly cause problems here, probably, but observe this contrived function.

    sub foo { my $self = shift if (ref $_[0]); $self++ unless ref $self; my($arg1,$arg2,$arg3)=@_; print "($self)($arg1)($arg2)($arg3)\n"; } foo(qw(a1 a2 a3)); foo(qw(a3 a4 a5)); Output: (1)(a1)(a2)(a3) (2)(a3)(a4)(a5)

    Note how $self survived between calls and was around to increment. Bugs of this nature can be horrible to track down. It's also frowned upon to try to use this for static variables - use a closure or wrap the subroutine in an additional lexical scope to have "static" variables scoped to the subroutine. Besides, it's tougher to read this way. In general, it's best to always avoid my $foo if $something constructs, except for obfuscated contests or the like.

      Thanks jimt, I've updated that example to use:
      my $self = ref $_[0] ? shift : undef;
      which doesn't seem to suffer from the largest of the problems. I've also added some clarifications about when it will and won't work.

      As an aside, you would have a very hard time convincing me that this behavior of my and conditionals is anything but a bug in Perl. :)

      First of all, there's the fact that this won't operate properly if it's called as a class method such as Some::Package->foo( qw(arg1 arg2 arg3) ); $_[0] is a string, in that case.

      Secondly, it breaks down if you pass in a normal reference as your first argument. foo({'hashkey' => 'hashval'}, qw(arg4 arg5)); You end up setting a normal arrayref as $self.

      I can get around some of those problems. First, if we assume that the subroutine was written as a function originally, and had no concept of self:

          shift if UNIVERSAL::isa ($_[0], __PACKAGE__);

      We can also deal with the possibility that we need self for the class name (eg, incase soemone calls it as a method to override inherited routines, but there's still existing code that has code that assumes it's a function:

      my $self = __PACKAGE__; $self = shift if UNIVERSAL::isa ($_[0], __PACKAGE__);

      It completely handles your first issue, however, this will break if you have a method that takes as its first argument an item of its same type (and someone tries calling it as a function), or if someone does some sort of multiple inheritance, that results in the first argument inheriting from this class (and then calls it as a function). It also adds an additional problem case where a function argument that's a string containing the name of a package that inherits the package in question is assumed to be the 'Class->method()' syntax..

Re^2: shift vs @_
by Zadeh (Beadle) on Oct 02, 2006 at 21:05 UTC
    Besides style, is there any performance penalty paid by shift? In the docs I see: "Shifts the first value of the array off and returns it, shortening the array by 1 and moving everything down." My reading of this makes me think that shift is potentially an expensive operation, because it has to "shift" the front of the area off, and then copy the remaining elements all back one position.

      This has been covered a few times. Check out:

      The bottom line? Perl implements arrays by creating a block of memory, and then pointing the beginning of the array as an offset into that block. When you try to unshift too much onto the beginning of the array such that we run out of room at that end of the chunk of memory, perl goes to allocate more memory, and, again, keeps a chunk free at the beginning. However, if you're shifting off the array until it's empty, perl just keeps incrementing the "beginning" pointer until the beginning and end point at the same place, meaning a length of zero. There is no copying here whatsoever.

        Thankyou. The statement in that second link about it being an O(1) operation is exactly what I was looking for--I was afraid it might be O(n).
      They seem to be about the same. shift comes out slightly ahead in this test, perhaps because it simplifies the loop.
      #!/usr/bin/perl use warnings; use strict; use Benchmark; timethese(1_000_000, { 'use_shift' => sub { sub_with_shift(0..9) }, 'use_list' => sub { sub_with_list(0..9) }, 'use_direct' => sub { sub_with_direct(0..9) }, }); sub sub_with_shift { my $sum = 0; while (@_) { $sum += shift; } $sum; } sub sub_with_list { my(@a)=@_; my $sum = 0; $sum += $_ for @a; $sum; } sub sub_with_direct { my $sum = 0; $sum += $_ for @_; $sum; }
      Benchmark: timing 1000000 iterations of use_direct, use_list, use_shift...
      use_direct:  7 wallclock secs ( 6.48 usr + -0.01 sys =  6.47 CPU) @ 154559.51/s (n=1000000)
        use_list: 10 wallclock secs ( 9.85 usr +  0.06 sys =  9.91 CPU) @ 100908.17/s (n=1000000)
       use_shift:  6 wallclock secs ( 6.48 usr +  0.01 sys =  6.49 CPU) @ 154083.20/s (n=1000000)
      
        #!/usr/bin/perl use warnings; use strict; use Benchmark; timethese(1_000_000, { 'use_shift' => sub { sub_with_shift(0..2) }, 'use_list' => sub { sub_with_list(0..2) }, 'use_direct' => sub { sub_with_direct(0..2) }, }); sub sub_with_shift { my ($one, $two, $three) = (shift, shift, shift); my $sum = $one+$two+$three; } sub sub_with_list { my ($one, $two, $three) = @_; my $sum = $one+$two+$three; } sub sub_with_direct { my $sum = $_[0] + $_[1] + $_[2]; }
        i think my example better reflects the core of the problem
        Sorry i forgot result Benchmark: timing 1000000 iterations of use_direct, use_list, use_shif +t... use_direct: 0 wallclock secs ( 0.57 usr + 0.00 sys = 0.57 CPU) @ 17 +54385.96/s (n=1000000) use_list: 1 wallclock secs ( 0.86 usr + 0.00 sys = 0.86 CPU) @ 11 +62790.70/s (n=1000000) use_shift: 2 wallclock secs ( 0.96 usr + 0.00 sys = 0.96 CPU) @ 10 +41666.67/s (n=1000000)
      Besides style, is there any performance penalty paid by shift?

      Yes; if you do it repeatedly hundreds of thousands of times in a tight loop, you might slow down your program as much as performing one IO operation. In other words, none that you will ever notice.

      Interesting point, but it would be a lot easier to shift the zeroth index point up one element, presuming that the array members are themselves string descriptors or other pointers. That plus decrementing the array size would be pretty cheap.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://575930]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others drinking their drinks and smoking their pipes about the Monastery: (5)
As of 2024-03-29 14:00 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found