Juan A. Navarro
Juan A. Navarro

Reputation: 11045

How to call a subroutine with a variable pre-assigned to some value?

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

Answers (3)

Axeman
Axeman

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:

  1. I declare our ( $k, $v ) in the implementing package.
  2. In import I allow packages to export those symbols and alias them in the receiving package: *{$import_caller.'::'.$name} = \*{ $name };
  3. 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

LeoNerd
LeoNerd

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 localise $_:

sub process_and_store(&@)
{
  my $code = shift;
  foreach my $item (@_) {
    local $_ = $item;
    $code->();
  }
}

Upvotes: 2

flesk
flesk

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

Related Questions