Reputation: 11045
In Perl, when one uses the sort
function with a custom comparison, the variables $a
and $b
are already assigned to the current pair of elements to compare, e.g. in:
@decreasing = sort { $b <=> $a } @list;
How can I write other subroutines with a similar functionality? For example, imagine that I want to write sort of process_and_store
function that does something special with each item of a list and then stores it in a database; and where the variable $item
is already assigned to the current item being processed. I would like to write for example something like:
process_and_store { do_something_with($item); } @list;
Rather than
process_and_store { my $item = shift; do_something_with($item); } @list;
How should I go about doing this?
UPDATE: For completeness, although flesk's answer works without problems, in order to “properly” localize the changes I make to the $item
variable I had to follow the advice from Axeman. In SomePackage.pm
I placed something like:
package SomePackage;
use strict;
require Exporter;
our @ISA = qw/Exporter/;
our @EXPORT = qw(process_and_store);
our $item;
sub import {
my $import_caller = caller();
{ no strict 'refs';
*{ $import_caller . '::item' } = \*item;
}
# Now, cue Exporter!
goto &{ Exporter->can( 'import' ) };
}
sub process_and_store (&@) {
my $code = shift;
for my $x (@_) {
local *item = \$x;
$code->();
print "stored: $item\n"
}
}
1;
Then I call this from main.pl
with something like:
#!/usr/bin/perl -w
use strict;
use SomePackage;
process_and_store { print "seen: $item\n"; } (1, 2, 3);
And get the expected result:
seen: 1
stored: 1
seen: 2
stored: 2
seen: 3
stored: 3
Upvotes: 2
Views: 178
Reputation: 29854
In my "associative array" processing library, I do something similar. The user can export the variables $k
and $v
(key-value) so that they can do things like this:
each_pair { "$k => $v" } some_source_list()
Here's how I do it:
our ( $k, $v )
in the implementing package.import
I allow packages to export those symbols and alias them in the
receiving package: *{$import_caller.'::'.$name} = \*{ $name };
In the pair processors, I do the following:
local *::_ = \$_[0];
local *k = \$_[0];
local *v = \$_[1];
@res = $block->( $_[0], $_[1] );
Thus $k
and $v
are aliases of what's in the queue. If this doesn't have to be the case, then you might be happy enough with something like the following:
local ( $k, $v ) = splice( @_, 0, 2 );
local $_ = $k;
But modifiable copies also allow me to do things like:
each_pair { substr( $k, 0, 1 ) eq '-' and $v++ } %some_hash;
UPDATE:
It seems that you're neglecting step #2. You have to make sure that the symbol in the client package maps to your symbol. It can be as simple as:
our $item;
sub import {
my $import_caller = caller();
{ no strict 'refs';
*{ $import_caller . '::item' } = \*item;
}
# Now, cue Exporter!
goto &{ Exporter->can( 'import' ) };
}
Then when you localize your own symbol, the aliased symbol in the client package is localized as well.
The main way that I can see that it would work without the local
, is if you were calling it from the same package. Otherwise, $SomePackage::item
and $ClientPackage::item
are two distinct things.
Upvotes: 4
Reputation: 8542
The $a
and $b
variables are special in Perl; they're real global variables and hence exempt from use strict
, and also used specifically by the sort()
function.
Most other similar uses in Perl would use the $_
global for this sort of thing:
process_and_store { do_something_with( $_ ) } @list;
Which is already handled by the normal $_
rules. Don't forget to local
ise $_
:
sub process_and_store(&@)
{
my $code = shift;
foreach my $item (@_) {
local $_ = $item;
$code->();
}
}
Upvotes: 2
Reputation: 7579
I think it's a bit of a hack, but you could do something like this:
#!/usr/bin/perl
use strict;
use warnings;
my $item;
sub process_and_store(&@) {
my $code = shift;
for (@_) {
$item = $_;
&$code();
}
undef $item;
}
The thing is, $item
has to be a global scalar for this to work, so process_and_store
has to update that scalar while looping over the list. You should also undef $item
at the end of the sub routine to limit any potential side-effects. If I were to write something like this, I'd tuck it away in a module and make it possible to define the iterator variable, so as to limit name conflicts.
Test:
my @list = qw(apples pears bananas);
process_and_store { do_something_with($item) } @list;
sub do_something_with {
my $fruit = shift;
print "$fruit\n";
}
Output:
apples
pears
bananas
Upvotes: 2