On 05/20/2012 03:28 PM, Shawn H Corey wrote:
On 12-05-20 06:15 PM, David Christensen wrote:
If your subroutine needs to know how many arguments were passed, the
former style (assignment) makes this trivial. Once @_ has been shifted
(latter style), I don't know an easy way to determine if zero or one
argument was passed (stack crawling?).
If it needs to know, assign the whole thing to an array. Otherwise, your
interface is badly written and hard to understand.

My best subroutines do argument checking, including the number and/or type(s) of arguments. So, they need to know. And, many of my subroutines take arguments other than a list. For example, let's say I have a subroutine with the following signature:

        $result = reduce CODEREF, LIST

The implementation will likely start with:

        confess "argument 0 (CODEREF) is required" unless @_;
        my $coderef = shift;

At this point, assigning what remains of @_ (e.g. LIST) to an internal variable is a waste -- the routine can access @_ directly.


But, this is Perl and there are more than two ways to do it -- you can
also access the argument array (@_) directly within your subroutine:
1. Might save some CPU cycles and/or memory. Be sure to benchmark this
claim, to see if it's worth the loss of clarity provided by well-named
internal variables.
Irrelevant. Do not micro-optimize. Optimize only in response to need and
only after profiling to determine where to apply your efforts.

I've updated function_arguments.pl with Benchmark, below. f_direct() is the fastest, f_shift() is in the middle (12% slower), and f_assign() is the slowest (37%).


2. Provides call-by-reference semantics -- your subroutine can modify
the caller's variables (intentionally or otherwise -- beware!).
Yes, that's why you use call-by-reference. But once you assign a value
to a my variable, it is independent of other variables. It's only
references that allow you to modify the caller's variables.

That's what I thought, until I started writing subroutines that modified @_ and stomped on the the caller's variables. See ref_vs_direct.pl, below.


3. Auto-vivifies missing arguments (?).
I'm not sure what you mean by this but I think my reply to #2 covers it.

Look carefully at the "f_direct()" case in function_arguments.pl, below. When "f_direct()" is called, @_ is empty and yet "$_[0] += 1" works and evaluates to 1. Therefore, it appears that elements of @_ are auto-vivified when the caller doesn't provide enough arguments.


4. Blows up if the subroutine tries to modify read-only arguments.
Again, see my reply to #2.

Look carefully at the "f_direct(10)" case in function_arguments.pl, below. When f_direct(10) is called, $_[0] corresponds to the numeric literal "10", which cannot be modified by "$_[0] += 1". So, an exception is thrown.


David

--

2012-05-20 19:53:00 dpchrist@p43400e ~/sandbox/perl
$ cat function_arguments.pl
#!/usr/bin/perl
use strict;
use warnings;
use Benchmark           qw( :all );
use Data::Dumper;
$Data::Dumper::Terse = 1;
$| = 1;
sub f_assign {
    my ($a) = @_;
    $a += 1;
}
sub f_direct {
    $_[0] += 1;
}
sub f_shift {
    my $a = shift;
    $a += 1;
}
# main
{
    for my $f (qw( f_assign f_shift f_direct )) {
        my $y = eval { no strict 'refs'; $f->() };
        print "$f() ", $@
            ? "throws exception: $@"
            : 'returns ' . Data::Dumper->Dump([$y], [qw(y)]);
        $y = eval { no strict 'refs'; $f->(10) };
        print "$f(10) ", $@
            ? "throws exception: $@"
            : 'returns ' . Data::Dumper->Dump([$y], [qw(y)]);
        my $xx = my $x = 100;
        print '$x = ', Data::Dumper->Dump([$x], [qw(x)]);
        $y = eval { no strict 'refs'; $f->($x) };
        print "$f(\$x) ", $@
            ? "throws exception: $@"
            : 'returns ' . Data::Dumper->Dump([$y], [qw(y)]);
        print '### side effect: $x is now ',
            Data::Dumper->Dump([$x], [qw(x)])
            if $x != $xx;
    }
    my $z = 0;
    timethese($ARGV[0] || 1_000, {
        'f_assign'      => sub { f_assign($z) },
        'f_shift'       => sub { f_shift($z)  },
        'f_direct'      => sub { f_direct($z) },
    });
    print '$z = ', Data::Dumper->Dump([$z], [qw(z)]);
}

2012-05-20 19:53:25 dpchrist@p43400e ~/sandbox/perl
$ time perl function_arguments.pl 10000000
f_assign() returns 1
f_assign(10) returns 11
$x = 100
f_assign($x) returns 101
f_shift() returns 1
f_shift(10) returns 11
$x = 100
f_shift($x) returns 101
f_direct() returns 1
f_direct(10) throws exception: Modification of a read-only value attempted at function_arguments.pl line 13.
$x = 100
f_direct($x) returns 101
### side effect: $x is now 101
Benchmark: timing 10000000 iterations of f_assign, f_direct, f_shift...
f_assign: 6 wallclock secs ( 6.85 usr + 0.00 sys = 6.85 CPU) @ 1459854.01/s (n=10000000) f_direct: 6 wallclock secs ( 4.99 usr + 0.00 sys = 4.99 CPU) @ 2004008.02/s (n=10000000) f_shift: 6 wallclock secs ( 5.58 usr + 0.00 sys = 5.58 CPU) @ 1792114.70/s (n=10000000)
$z = 10000000

real    0m43.384s
user    0m43.371s
sys     0m0.016s






2012-05-20 22:01:37 dpchrist@p43400e ~/sandbox/perl
$ cat ref_vs_direct.pl
#!/usr/bin/perl
use strict;
use warnings;
use Carp;
use Test::More  tests => 6;

# (void) sum_of_squares SUMREF, LIST
# (void) sum_of_squares SUM, LIST

sub sum_of_squares {
    confess "argument 0 (SUMREF or SUM) required" unless @_;
    if (ref $_[0]) {
        my $rs = shift;
        map {$$rs += $_ * $_} @_;
    }
    else {
        map {$_[0] += $_ * $_} @_[1 .. $#_];
    }
    return;
}
ok(!defined eval {sum_of_squares()});                           #     1
ok($@ =~ /argument 0 .SUMREF or SUM. required/);                #     2
my $sum = 0;
ok(!sum_of_squares(\$sum, 1, 2, 3));                            #     3
ok($sum == 14);                                                 #     4
ok(!sum_of_squares($sum, 4, 5, 6));                             #     5
ok($sum == 91);                                                 #     6

2012-05-20 22:01:39 dpchrist@p43400e ~/sandbox/perl
$ perl ref_vs_direct.pl
1..6
ok 1
ok 2
ok 3
ok 4
ok 5
ok 6


--
To unsubscribe, e-mail: beginners-unsubscr...@perl.org
For additional commands, e-mail: beginners-h...@perl.org
http://learn.perl.org/


Reply via email to