Object Oriented Perl: Calling another function within the same class - perl

I have a to process some files in a directory.
So, I am using non-OO Perl code as below (just the important snippets are printed below):
#!/usr/bin/perl
use strict;
use warnings;
my $dnaFilesDirectory = "./projectGeneSequencingPfzr";
my %properties = &returnGeneticSequences($dnaFilesDirectory);
sub returnGeneticSequences {
my $dnaDirectory = shift;
my #dnaFiles = ();
opendir(DNADIR, $dnaFilesDirectory) or die "Cannot open directory:$!";
#dnaFiles = readdir(DIR);
foreach my $file (#dnaFiles) {
my $dnaFilePath = $dnaFilesDirectory."\/".$file;
if($file =~ /dna_file.*\.dnaPrj/) {
my %diseaseStages = &returnDiseasesStages($dnaFilePath);
## Do some data analysis on the %diseaseStages Hash;
}
}
}
sub returnDiseasesStages {
my $dnaFile = shift;
## Do something with DNA file and build a hash called %diseasesStagesHash;
return %diseasesStagesHash;
}
The above code works fine.
But we have to create the equivalent OO Perl code for the above functions.
I am trying to do the following, but it does not seem to work. Obviously, I am doing something wrong in calling the class method returnDiseasesStages from returnGeneticSequences.
#!/usr/bin/perl
use strict;
use warnings;
package main;
my $obj = GeneticSequences->new(dnaFilesDir => "./projectGeneSequencingPfzr");
$obj->returnGeneticSequences();
package GeneticSequences;
sub new {
my $class = shift;
my $self = {
dnaFilesDir => "dnaFilesDir",
#_,
};
return (bless($self,$class));
}
sub returnGeneticSequences {
my $self = shift;
my $dnaFilesDirectoryGS = $self->{dnaFilesDir};
my #dnaFiles = ();
opendir(DNADIR,$dnaFilesDirectoryGS) or die "Cannot open directory:$!";
#dnaFiles = readdir(DIR);
foreach my $file (#dnaFiles) {
my $dnaFilePath = $dnaFilesDirectory."\/".$file;
if($file =~ /dna_file.*\.dnaPrj/) {
my $gsObj = GeneticSequences->new();
my %diseaseStages = $gsObj->returnDiseasesStages($dnaFilePath);
## Do some data analysis on the %diseaseStages Hash;
}
}
}
sub returnDiseasesStages {
my $dnaFile = shift;
##Do something with DNA file and build a hash called %diseasesStagesHash;
return %diseasesStagesHash;
}
Please help me understand what I am doing wrong.

The syntax
$gsObj->returnDiseasesStages($dnaFilePath)
is equivalent to the syntax
returnDiseasesStages($gsObj, $dnaFilePath)
(with Perl checking the reference type of $gsObj to see what package to search for the returnDiseasesStages function in).
So your returnDiseasesStages function should expect two arguments:
sub returnDiseasesStages {
my ($self, $dnaFile) = #_;
...
}

Related

Link a variable to a class attribute in Perl

This question was born out of another (Completely destroy all traces of an object in Perl). After seeing some of the comments I believe I have narrowed the problem down to the "real" issue.
I'm looking for a simple way to link a variable to a class attribute in Perl so that whenever the attribute is modified, the variable will be automatically updated.
ex (some pseudo code):
# Create a file object
my $file = File->new();
# Get the text
my $text = $file->text();
# prints 'hello'
print $text;
# Set the text
$file->text('goodbye');
# prints 'goodbye'
print $text;
Also I want the $text variable to be read only so that you cannot inadvertently modify the text attribute of the file.
Use tie:
#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };
{ package File;
sub new {
bless ['hello'], shift
}
sub text {
my $self = shift;
if (#_) {
$self->[0] = shift;
} else {
return $self->[0]
}
}
}
{ package FileVar;
use Tie::Scalar;
use parent qw( -norequire Tie::StdScalar );
sub TIESCALAR {
my ($class, $obj) = #_;
bless \$obj, $class
}
sub FETCH {
my $self = shift;
${$self}->text()
}
sub STORE {
die 'Read only!';
# Or, do you want to change the object by changing the var, too?
my ($self, $value) = #_;
${$self}->text($value);
}
}
my $file = 'File'->new();
tie my $text, 'FileVar', $file;
say $text;
$file->text('goodbye');
say $text;
# Die or change the object:
$text = 'Magic!';
say $file->text;

How can I pass a module's function as a reference to another module in Perl?

How can I pass a reference to a module's function as parameter in a function call of another module?
I tried the following (simple example):
This is the module that has a function (process_staff) that takes as a parameter a function reference (is_ok).
#!/usr/bin/perl
use strict;
use warnings;
package Objs::Processing;
sub new {
my ($class) = #_;
bless {} ;
}
sub process_staff {
my ($employee, $func) = #_;
if($func->is_ok($employee)) {
print "Is ok to process\n";
}
else {
print "Not ok to process\n";
}
}
1;
This is the module that implements the passed function (is_ok)
#!usr/bin/perl
use strict;
use warnings;
package Objs::Employee;
my $started;
sub new {
my ($class) = #_;
my $cur_time = localtime;
my $self = {
started => $cur_time,
};
print "Time: $cur_time \n";
bless $self;
}
sub get_started {
my ($class) = #_;
return $class->{started};
}
sub set_started {
my ($class, $value) = #_;
$class->{started} = $value;
}
sub is_ok {
my ($emp) = #_;
print "In is ok I received:\n";
use Data::Dumper;
print Dumper($emp);
return 1;
}
This is my test script that I run:
#!/usr/bin/perl
use strict;
use warnings;
use Objs::Manager;
use Objs::Processing;
my $emp = Objs::Manager->new('John Smith');
use Data::Dumper;
print Dumper($emp);
my $processor = Objs::Processing->new();
$processor->process_staff(\&$emp->is_ok); #error is here
I get a:
Not a CODE reference at testScript.pl line 14.
I also tried: $processor->process_staff(\&$emp->is_ok()); but also still does not work.
What am I doing wrong here
You appear to want to pass an object and a method to call on it; the easiest way to do that would be:
$processor->process_staff( sub { $emp->is_ok } );
where process_staff looks like:
sub process_staff {
my ($self, $func) = #_;
if ( $func->() ) {
...
or you can pass the reference and the object separately:
sub process_staff {
my ($self, $emp, $method) = #_;
if ( $emp->$method() ) {
...
$processor->process_staff( $emp, $emp->can('is_ok') );
I think this could work with:
$processor->process_staff(\&Objs::Employee::is_ok);
where you pass in the method ref.
and where you currently have
if( $func->is_ok($employee) ) {
you need
if( $func->( $employee ) ) {
This is because you cannot reference named methods simply from an object, by the syntax \&$obj->method.
However, in your example code it is not at all clear why you don't do this instead:
if( $employee->is_ok() ) {
in which case you would not need to reference the method to call in process_staff at all. There are also other ways to achieve the same method indirection that might give you better encapsulation in future.
In this expression:
$processor->process_staff(\&$emp->is_ok);
You are saying "call the method $emp->is_ok, take the return value, treat it as a CODE reference, dereference it, and return a reference to that. That doesn't work, since the return value from that sub is not a CODE reference.
To do what you want, you can use a reference to an anonymous sub to wrap the call to your object method:
$processor->process_staff( sub { $emp->is_ok } );
You can pass anonymous coderef which returns result from desired method,
$processor->process_staff(sub{ $emp->is_ok(#_) });
#_ can be dropped as is_ok method doesn't take any arguments.
It's not specifically what you asked for, but I think you simply need the following:
sub process_staff {
my ($self, $emp) = #_;
if ($emp->is_ok()) {
print "Is ok to process\n";
}
else {
print "Not ok to process\n";
}
}
$processor->process_staff($emp);

How do I insert new fields into $self in Perl, from a File::Find callback

In a Perl object, I'm trying to add a new field into $self from within a File::Find wanted() sub.
use File::Find;
sub _searchForXMLDocument {
my ($self) = #_;
if($_ =~ /[.]+\.xml/) {
$self->{_xmlDocumentPath} = $_;
}
}
sub runIt{
my ($self) = #_;
find (\&_searchForXMLDocument, $self->{_path});
print $self->{_xmlDocumentPath};
}
_searchForXMLDocument() searches for an XML Document within $self->{_path} and is supposed to append that XML path to $self->{_xmlDocumentPath} but when I try to print it, it remains uninitialized. How do I add the field in $self?
Use of uninitialized value in print at /home/scott/workspace/CCGet/XMLProcessor.pm line 51.
You aren't calling _searchForXMLDocument() in an OO manner, so your $self object isn't being passed to it. This should do the trick now. Use a closure for your method and you have access to $self;
sub runIt{
my ($self) = #_;
my $closure = sub {
if($_ !~ m/[.]+\.xml/) {
$self->{_xmlDocumentPath} = $_;
}
};
find(\&$closure, $self->{_path});
print $self->{_xmlDocumentPath};
}
The first argument to find() needs to carry two pieces of information: the test condition, and the object you're working with. The way to do this is with a closure. The sub { ... } creates a code ref, like you get from \&_searchForXMLDocument, but the closure has access to lexical variables in the enclosing scope, so the current object ($self) is associated with the closure.
sub _searchForXMLDocument {
my ($self) = #_;
if($_ =~ /[.]+\.xml/) {
$self->{_xmlDocumentPath} = $_;
}
}
sub runIt{
my ($self) = #_;
find (sub { $self->_searchForXMLDocument (#_) }, $self->{_path});
print $self->{_xmlDocumentPath};
}
I think you're looking for something like this:
package XMLDocThing;
use strict;
use warnings;
use English qw<$EVAL_ERROR>;
use File::Find qw<find>;
...
use constant MY_BREAK = do { \my $v = 133; };
sub find_XML_document {
my $self = shift;
eval {
find( sub {
return unless m/[.]+\.xml/;
$self->{_xmlDocumentPath} = $_;
die MY_BREAK;
}
, $self->{_path}
);
};
if ( my $error = $EVAL_ERROR ) {
die Carp::longmess( $EVAL_ERROR ) unless $error == MY_BREAK;
}
}
...
# meanwhile, in some other package...
$xmldocthing->find_XML_document;
You pass a closure to find and it can access $self from the containing scope. File::Find::find has no capacity to pass in baggage like objects.

How do I read args passed to the constructor and args passed by `use Module` in Perl?

Currently I am making a new module and I was wondering how could I implement in my module 2 things.
We often see the use like:
use My::Module qw(something);
for example:
use CGI::Carp qw(fatalsToBrowser);
So the first question is, how do i
retrieve this, i mean wether the
user has specified anything and what
he specified ?
Second question is, How do i pass and read the args
directly on the constructor like
this:
my $my_module = My::Module->new(arg1,arg2,arg3);
AS requested on the comment the simple module test code:
package My::Module;
# $Id$
use strict;
use Carp;
sub new {
my $class = shift;
my $self = {};
$self->{ARG1} = undef;
$self->{ARG2} = undef;
$self->{ARG3} = undef;
$self->{ARG4} = undef;
bless($self,$class);
return $self;
}
sub arg1 {
my $self = shift;
if (#_) { $self->{ARG1} = shift }
return $self->{ARG1};
}
sub arg2 {
my $self = shift;
if (#_) { $self->{ARG2} = shift }
return $self->{ARG2};
}
sub arg3 {
my $self = shift;
if (#_) { $self->{ARG3} = shift }
return $self->{ARG3};
}
sub arg4 {
my $self = shift;
if (#_) { $self->{ARG4} = shift }
return $self->{ARG4};
}
sub dump {
my $self = shift;
require Data::Dumper;
my $d = Data::Dumper->new([$self], [ref $self]);
$d->Deepcopy(1);
return $d->Dump();
}
1; # so the require or use succeeds
perldoc -f use explains that the use keyword is simply loading a module during compile-time, and then calling ->import on it. The arguments a caller gave to the use statement will be passed to the import method call.
As for your second question: constructors are just methods. Getting their arguments works like it does for any other method or function, using the #_ variable.
import subroutine gets the arguments passed in a use. The following code samples should help you.
File: My/Module.pm
package My::Module;
use warnings;
use strict;
use Data::Dumper;
sub import {
my ( $package, #args ) = #_;
print Dumper \#args;
}
1;
File: module.pl
#!/usr/bin/env perl
use warnings;
use strict;
use My::Module qw(something);
If you are programming an object oriented module, you may try Moose which will save you lots of time.

How can I use a code ref as a callback in Perl?

I have the following code in my class :
sub new {
my $class = shift;
my %args = #_;
my $self = {};
bless( $self, $class );
if ( exists $args{callback} ) {
$self->{callback} = $args{callback};
}
if ( exists $args{dir} ) {
$self->{dir} = $args{dir};
}
return $self;
}
sub test {
my $self = shift;
my $arg = shift;
&$self->{callback}($arg);
}
and a script containing the following code :
use strict;
use warnings;
use MyPackage;
my $callback = sub {
my $arg = shift;
print $arg;
};
my $obj = MyPackage->new(callback => $callback);
but I receive the following error:
Not a CODE reference ...
What am I missing? Printing ref($self->{callback}) shows CODE. It works if I use $self->{callback}->($arg), but I would like to use another way of invoking the code ref.
The ampersand is binding just to $self and not the whole thing. You can do curlies around the part that returns the reference:
&{$self->{callback}}($arg);
But the
$self->{callback}->($arg);
is generally considered cleaner, why don't you want to use it?