dzookatz
dzookatz

Reputation: 213

Manipulate Perl object by reference in subroutine

I have a Perl program and packages Worker and Log.

The Worker does almost all calculations, and I want to pass an object by reference to the Worker subroutine, as well as some other parameters (scalar and an array). I have seen examples like this and this.

They handle this by putting @_ in subs, then manipulating the object. I also found a way to manipulate them by using the index, like @{$_[i]}. Problem is, when I try the code like so, I get an error: Can't call method "write" on unblessed reference at ...

Code snippets below.

Main:

use strict;
use warnings;
use Log;
use Worker;

my $log = Log->new();
my $worker = Worker->new();
my $scalar = "SomeURLhere";
my @array = ('red','blue','white');

# I do some stuff with $log object
#...
# Now I want to pass data to the Worker
$worker->subFromWorker($scalar, \$log, \@array);

Worker:

use strict;
use warnings;
package Worker;

sub new {
    my $class = shift;
    my $self = {};
    bless $self, $class;
    return $self;
}

sub subFromWorker{
    my ($self) = shift;
    my $scalar = $_[0];
    #my ($log) = $_[1];
    my @array = @{$_[2]};

    foreach my $item (@array){
        print $item;
    }

    $_[1]->write("The items from url $scalar are printed.");

    #Same thing happens if I use $log here
}

In C#, this is handled in a different way - you can send a parameter to a method by value or by reference, and then do what you want in a specialized method (method is pre-written to handle parameters by reference or value). I thought that in Perl sending using \parameter will send the reference.

Upvotes: 3

Views: 3600

Answers (3)

Borodin
Borodin

Reputation: 126722

You have read about the issues that prevent your program from working, but there are a few other things you should be aware of

  • Perl lexical identifiers and subroutine/method names consist of alphanumerics and underscore. Capital letters are reserved for global identifiers, such as package names like Worker and Log.

  • Packages that you use or require should end with the statement 1; so as to return a true value when they are imported, otherwise your program may fail to compile.

  • If a subroutine that you are writing happens to be a method, then it is clearest to start it by shifting off the $self parameter and making a copy of the rest:

    my $self = shift;
    my ($p1, $p2, $p3) = @_;
    

    It is rare to use elements of @_ directly unless you're desperate for the minimal speed bonus

  • It is usually best to work directly with an array reference rather than copying the array, especially if it may be large.

Here is how I would code your program and associated modules:

program.pl

use strict;
use warnings;

use Worker;
use Log;

my $log    = Log->new;
my $worker = Worker->new;

my $scalar = 'SomeURLhere';
my @array  = qw/ red blue white /;

$worker->worker_method($scalar, $log, \@array);

Worker.pm

use strict;
use warnings;

package Worker;

sub new {
    my $class = shift;
    my $self  = {};
    bless $self, $class;
    return $self;
}

sub worker_method {
    my $self   = shift;
    my ($scalar, $log, $array) = @_;

    foreach my $item (@$array) {
        print $item, "\n";
    }

    $log->write("The items from URL $scalar are printed.");
}

1;

Log.pm

use strict;
use warnings;

package Log;

sub new {
    my $class = shift;
    bless {}, $class;
}

sub write {
    my $self   = shift;
    my ($text) = @_;

    print "Logging: $text\n"
}

1;

Output

red
blue
white
Logging: The items from URL SomeURLhere are printed.

Upvotes: 3

Hunter McMillen
Hunter McMillen

Reputation: 61512

A more common pattern is to use List assignment to unpack @_ into multiple variables all at once:

sub subFromWorker {
   my ($self, $scalar, $log_ref, $array) = @_;
   ...
}

In reference to your specific problem:

my $log = Log->new();

$log is already a reference to your object, using \$log creates a reference to that reference which is not probably not what you want. You can handle this two ways:

  1. only pass $log:
    • $worker->subFromWorker($scalar, $log, \@array);
  2. dereference $log in subFromWorker before calling functions on it:
    • $$log_ref->write('...');

Upvotes: 0

Dave Cross
Dave Cross

Reputation: 69244

Objects are references. References are scalar values.

If you want to pass arrays or hashes into a subroutine then you usually want to pass references to them - because Perl parameter passing works far better with scalar values.

But $log is already a reference to your object. Therefore you don't need to take a reference to it. You end up passing a reference to a reference. So when you copy that parameter into $log inside your subroutine you have an extra, unnecessary, level of references.

The fix is to just pass the $log scalar into the subroutine.

$worker->subFromWorker($scalar, $log, \@array); # $log, not \$log

Everything else will then work fine.

Upvotes: 7

Related Questions