How to initialize a specific class variable while creating an object? - perl

So far I always relied on the order of variables in a class, but now I'm trying to initialize those variables in a shuffled order.
For example, this is what I normally do while creating an object.
my $person1 = Person->new ('Fernando', 'Alonso', 36);
And this is what I'm trying to achieve.
my $person2 = Person->new (Age => 36, FistName => 'Fernando', LastName => 'Alonso');
I tried => regarding to several documents (e.g. perldoc) I saw, but they didn't return a complete example to me. However I don't actually work on the following script, it's a fair MCVE with the 'cliché' package Person.
use strict;
use warnings;
package Person;
sub new {
my $class = shift;
my $self = {
FirstName => shift,
LastName => shift,
Age => shift,
};
print "First name : $self->{FirstName}\n";
print "Last name : $self->{LastName}\n";
print "Age : $self->{Age}\n\n";
bless $self, $class;
return $self;
}
# Works well
my $person1 = Person->new ('Fernando', 'Alonso', 36);
# (?) How to assign 36 directly to $self->{Age}
my $person2 = Person->new (Age => 36, '', '');
The output is as follows.
First name : Fernando
Last name : Alonso
Age : 36
First name : Age
Last name : 36
Age :
So, how should I create the object to make explicit assignments to the class variables? If necessary, how should I modify package Person?
P.S. I avoid changing the variables after the object is created.

The original Person class constructor expects the arguments to contain values in a specific order, but you want to specify key-value pairs. This is easy enough by representing the input as a hash reference.
package Person;
sub new {
my $class = shift;
# read #_ as list of key-value pairs;
# set $self as reference to hash of these pairs
my $self = { #_ };
#my $self = { my %args = #_ }; # if you like your abstractions unbroken
bless $self, $class;
return $self;
}
If you want to restrict the keys that can be set to FirstName, LastName, and Age, you could do something like this.
package Person;
sub new {
my $class = shift;
my %args = #_;
my $self = {
FirstName => $args{FirstName},
LastName => $args{LastName},
Age => $args{Age},
};
bless $self, $class;
return $self;
}
and as a further enhancement, you could provide default values where the caller has not specified all of the expected keys
package Person;
sub new {
my $class = shift;
my %args = #_;
my $self = {
FirstName => $args{FirstName} // "Fred",
LastName => $args{LastName} // "Flinstone",
Age => $args{Age} // 42,
};
bless $self, $class;
return $self;
}

Related

Using a perl class object variables in destroy

Can we access object variables inside the destroy method of a perl class.
For eg : i have a perl class as below:
package Person;
sub new {
my $class = shift;
my $self = {
_firstName => shift,
_lastName => shift,
_ssn => shift,
};
# Print all the values just for clarification.
print "First Name is $self->{_firstName}\n";
print "Last Name is $self->{_lastName}\n";
print "SSN is $self->{_ssn}\n";
bless $self, $class;
return $self;
}
I create my object like below:
$object = new Person( "Mohammad", "Saleem", 23234345);
How can I make destroy function so that it will print me like
detroying Mohammad
The DESTROY method gets the same $self reference as its first parameter as all other methods in Perl OOP.
package Person
sub new { ... }
sub DESTROY {
my $self = shift;
print "destroying $self->{_firstName}";
}
package main;
{
my $foo = Person->new( 'foo', 'bar', 123 );
}
This will print
First Name is foo
Last Name is bar
SSN is 123
destroying foo

Perl module that accepts list and creates objects

I am working on an college problem ( in Perl ). We are working on creating modules and I need to write a simple module that "has get/set methods for four attributes: lastname, firstname, full_name and a list of children who are also person objects".
I think I have it down but it's the list of children who are also person objects that throws me. I guess the module needs to accept a list and then create a list of objects? Python is my core language so this one is throwing me. The get/set methods are working fine. Any ideas?
My module is here...
#!/usr/bin/perl
package Person;
sub new
{
my $class = shift;
my $self = {
_firstName => shift,
_lastName => shift,
};
bless $self, $class;
return $self;
}
sub setFirstName {
my ( $self, $firstName ) = #_;
$self->{_firstName} = $firstName if defined($firstName);
return $self->{_firstName};
}
sub getFirstName {
my( $self ) = #_;
return $self->{_firstName};
}
sub setLastName {
my ( $self, $lastName ) = #_;
$self->{_lastName} = $lastName if defined($lastName);
return $self->{_lastName};
}
sub getLastName {
my( $self ) = #_;
return $self->{_lastName};
}
sub getFullName {
my( $self ) = #_;
return $self->{_lastName}.",".$self->{_firstName};
}
1;
My code is here.....
#!/usr/bin/perl
use Person;
$object = new Person("Elvis","Presley");
# Get first name which is set using constructor.
$firstName = $object->getFirstName();
$lastName = $object->getLastName();
$fullname = $object->getFullName();
print "(Getting) First Name is : $firstName\n";
print "(Getting) Last Name is: $lastName\n";
print "(Getting) Full Name is: $fullname\n";
Just use a list of objects in the setter:
#! /usr/bin/perl
use warnings;
use strict;
use feature qw{ say };
{ package Person;
sub new {
my $class = shift;
my $self = {
_firstName => shift,
_lastName => shift,
_children => [],
};
return bless $self, $class
}
sub setFirstName {
my ($self, $firstName) = #_;
$self->{_firstName} = $firstName if defined $firstName;
return $self->{_firstName}
}
sub getFirstName {
my ($self) = #_;
return $self->{_firstName}
}
sub setLastName {
my ($self, $lastName) = #_;
$self->{_lastName} = $lastName if defined $lastName;
return $self->{_lastName}
}
sub getLastName {
my ($self) = #_;
return $self->{_lastName}
}
sub getFullName {
my ($self) = #_;
return $self->{_lastName} . ', ' . $self->{_firstName}
}
sub getChildren {
my ($self) = #_;
return #{ $self->{_children} }
}
sub setChildren {
my ($self, #children) = #_;
$self->{_children} = [ #children ];
}
}
my $object = 'Person'->new('Elvis', 'Presley');
# Get first name which is set using constructor.
my $firstName = $object->getFirstName;
my $lastName = $object->getLastName;
my $fullname = $object->getFullName;
$object->setChildren('Person'->new('Lisa', 'Presley'),
'Person'->new('Deborah', 'Presley'));
say "(Getting) First Name is: $firstName";
say "(Getting) Last Name is: $lastName";
say "(Getting) Full Name is: $fullname";
say "Children: ";
say $_->getFullName for $object->getChildren;
Note that there are modules to make building objects easier, e.g. Moo:
#! /usr/bin/perl
use warnings;
use strict;
use feature qw{ say };
{ package Person;
use Moo;
has first_name => (is => 'ro');
has last_name => (is => 'ro');
has full_name => (is => 'lazy');
has _children => (is => 'ro',
init_arg => undef,
default => sub { [] });
sub _build_full_name {
my ($self) = #_;
return $self->last_name . ', ' . $self->first_name
}
sub add_child {
my ($self, $child) = #_;
push #{ $self->_children }, $child
}
sub children {
my ($self) = #_;
return #{ $self->_children }
}
}
my $object = 'Person'->new(first_name => 'Elvis',
last_name => 'Presley');
# Get first name which is set using constructor.
my $firstName = $object->first_name;
my $lastName = $object->last_name;
my $fullname = $object->full_name;
$object->add_child($_) for 'Person'->new(first_name => 'Lisa',
last_name => 'Presley'),
'Person'->new(first_name => 'Deborah',
last_name => 'Presley');
say "(Getting) First Name is: $firstName";
say "(Getting) Last Name is: $lastName";
say "(Getting) Full Name is: $fullname";
say "Children: ";
say $_->full_name for $object->children;
The requirement means that there should be an attribute which can accommodate a collection of objects, so a reference to an array. This is defined in the constructor
sub new
{
my $class = shift;
my $self = {
_firstName => shift,
_lastName => shift,
_children => [ #_ ],
};
bless $self, $class;
return $self;
}
where [ ] creates an anonymous array and returns its reference, which is a scalar so it can be used for a hash value. The #_ in it contains the optional rest of the arguments (Person objects) after the class and names have been shift-ed.
Arguments need be checked but this gets hard with a plain list, when they are used positionally. Instead, consider using named parameters, ie. passing a hash(ref) to the constructor, with which it's easy to check which arguments have or have not been supplied.
Next, you need a method to add children to this attribute, for example
sub add_children {
my ($self, #children) = #_; # and check what's passed
push #{$self->{_children}}, #children;
return $self; # for chaining if desired
}
Finally, when you invoke this method you pass objects of the class Person to it
use warnings;
use strict;
use Person;
my $object = Person->new('Elvis', 'Presley');
my $child = Person->new('First', 'Last');
$object->add_children( $child );
or, if there is no use of a $child variable (object) in the rest of the code
$object->add_children( Person->new(...) );
You can add a list of children, add_children($c1, $c2, ...), for example to initially populate the data structure, or can add them individually as they appear.
A list of Person children can be used in the constructor as well
my $obj = Person->new('First', 'Last', $c1, $c2,...);
This gets clearer and far more flexible with mentioned named parameters, which are unpacked and sorted out in the constructor. But more to the point, once you learn the Perl's native OO system look at modules for this, best Moose and its light-weight counterpart Moo.
Comments
Always have use warnings; and use strict; at the beginning
Don't use the indirect object notation
my $obj = new ClassName(...); # DO NOT USE
See this post and this great example. The fact that it can be used to call a constructor is really an abuse of its other legitimate uses. Use a normal method call
my $obj = ClassName->new(...);
It's great that your college is teaching you Perl, but slightly disappointing that they're teaching you the "classic" version of Perl OO, when in the real world most OO work in Perl uses a framework like Moo or Moose.
For interest, I've included a Moo version of the Person object below:
package Person;
use Moo;
use Types::Standard qw[Str ArrayRef Object];
has first_name => (
is => 'rw',
isa => Str,
required => 1,
);
has last_name => (
is => 'rw',
isa => Str,
required => 1,
);
has children => (
is => 'rw',
isa => ArrayRef[Object],
);
sub full_name {
my $self = shift;
return $self->first_name . ' ' . $self->last_name;
}
1;
And here's a simple test program:
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
use Person;
my $elvis = Person->new(
first_name => "Elvis",
last_name => "Presley",
children => [Person->new(
first_name => 'Lisa Marie',
last_name => 'Presley',
)],
);
my $first_name = $elvis->first_name;
my $last_name = $elvis->last_name;
my $full_name = $elvis->full_name;
say "(Getting) First Name is : $first_name";
say "(Getting) Last Name is: $last_name";
say "(Getting) Full Name is: $full_name";
say "Elvis's first child is ", $elvis->children->[0]->full_name;
A few things to note:
Always include use strict and use warnings in your code
Always use Class->new in preference to new Class
Perl programmers prefer snake_case to camelCase
Moo likes you to use named parameters to an object constructor
Declarative attributes (using has) are far less repetitive than writing all your own getter and setter methods
People programmers tend to prefer a single method (foo() which can be used as both a getter and a setter over separate get_foo() and set_foo() methods.

How to create multiple objects in perl and access methods for same?

I was trying to create package with multiple methods and access them but it gives some hash values and expected?
following is code:
package student_data;
use strict;
use warnings;
use diagnostics;
use Carp;
# init cell with cell name
sub new_student{
my ($class,$args) = #_;
my $self = { student_name => $args->{student_name} || 'default_value',
reg_number => $args->{reg_number} || 'default_value',
dob => $args->{dob} || 'default_value',
subjects=> {}
};
bless $self, $class;
return $self;
}
sub new_subject{
my ($class,$args) = #_;
my $self = { subject_name => $args->{subject_name} || 'default_value',
credit => $args->{credit} || 'default_value',
grade => $args->{grade} || 'default_value',
};
#bless $self, $class;
return $self
}
sub add_subject{
my ($self,$args) = #_;
my $sub1 = $self->new_subject($args);
++$self->{subject}{$sub1};
return $self;
}
sub get_subject{
my ($self, $args) = #_;
#$self->{subject}{$sub1};
return $self;
}
1;
#use student_data;
my #all_students = ();
my $stud1= student_data->new_student({student_name =>"john",reg_number => "12"});
my $sub1 = student_data->new_subject({subject_name => "A" , credit => "3"}) ;
++$stud1->{subjects}{$sub1};
my $sub2 = student_data->new_subject({subject_name => "B" , grade => "50"}) ;
$stud1->add_subject($sub2);
push(#all_students, $stud1);
my $stud2= student_data->new_student({student_name =>"johnny",dob => "110388"});
my $sub3 = student_data->new_subject({subject_name => "B" , credit => "4"}) ;
++$stud1->{subjects}{$sub3};
my $sub4 = student_data->new_subject({subject_name => "A" , grade => "50"}) ;
$stud1->add_subject($sub4);
push(#all_students, $stud2) ;
my $et_stud = pop(#all_students);
print "\n student_name : $et_stud->{student_name} \n dob : $et_stud->{dob} \n subjects : $et_stud->{subjects}";
student_name : johnny
dob : 110388
subjects : HASH(0x10301b8)
but I expect :
student_name : johnny
reg_number : default_value dob : 110388
subject_name : A
credit : 3
grade : default_value
subject_name : B
credit : default_value grade : 50
I'm afraid you're really a very long way off from understanding Perl object-orientation, and it's really hard to know how to help you other than just writing a working version
I've changed the structure of a Student object so that its subjects field is an array of Subject objects
I could see the worth of using a hash to avoid duplicating subjects per student, but that would involve error handling when there is none in your original. However you have added use Carp so I included code to use it to warn of incorrect arguments in the constructors
Student.pm
package Student;
use strict;
use warnings 'all';
use Carp;
sub new {
my $class = shift;
my %args = #_;
my $self = {
name => delete $args{student_name} // 'default_value',
reg_number => delete $args{reg_number} // 'default_value',
dob => delete $args{dob} // 'default_value',
subjects => [],
};
carp 'Unexpected arguments ', join ', ', keys %args if keys %args;
return bless $self, $class;
}
sub name {
my $self = shift;
return $self->{name};
}
sub dob {
my $self = shift;
return $self->{dob};
}
sub reg_number {
my $self = shift;
return $self->{reg_number};
}
sub add_subject{
my $self = shift;
my ($subject) = #_;
my $subjects = $self->{subjects};
push #$subjects, $subject;
return $self; # So that add_subject may be chained
}
sub subjects {
my $self = shift;
#{ $self->{subjects} };
}
1;
Subject.pm
package Subject;
use strict;
use warnings 'all';
use Carp;
sub new {
my $class = shift;
my %args = #_;
my $self = {
name => delete $args{subject_name} // 'default_value',
credit => delete $args{credit} // 'default_value',
grade => delete $args{grade} // 'default_value',
};
carp 'Unexpected arguments ', join ', ', keys %args if keys %args;
return bless $self, $class;
}
sub name {
my $self = shift;
return $self->{name};
}
sub credit {
my $self = shift;
return $self->{credit};
}
sub grade {
my $self = shift;
return $self->{grade};
}
1;
main.pl
use strict;
use warnings 'all';
use Student;
use Subject;
my #all_students;
my $student;
$student = Student->new( student_name => 'john', reg_number => 12 );
$student->add_subject( Subject->new( subject_name => 'A', credit => 3 ) );
$student->add_subject( Subject->new( subject_name => 'B', grade => 50 ) );
push #all_students, $student;
$student = Student->new( student_name => 'johnny', dob => '110388' );
$student->add_subject( Subject->new( subject_name => 'B', credit => 4 ) );
$student->add_subject( Subject->new( subject_name => 'A', grade => 50 ) );
push #all_students, $student;
my $et_stud = pop #all_students;
printf "student_name: %s\n", $et_stud->name;
printf "reg_number: %s\n", $et_stud->reg_number;
printf "dob: %s\n", $et_stud->dob;
for my $subject ( $et_stud->subjects ) {
print "\n";
printf " subject_name: %s\n", $subject->name;
printf " credit: %s\n", $subject->credit;
printf " grade: %s\n", $subject->grade;
}
output
student_name: johnny
reg_number: default_value
dob: 110388
subject_name: B
credit: 4
grade: default_value
subject_name: A
credit: default_value
grade: 50
In the add_subject function you're using the unblessed object returned by new_subject() as a key:
my $sub1 = $self->new_subject($args);
++$self->{subject}{$sub1};
But the keys must be scalar in Perl, so the object is converted to a string like 'HASH(0x1a1c148)'.
If you want to store the objects, store them as values. For example, you might store an array reference in the object:
sub new_student {
my ($class, $args) = #_;
my #subjects;
return bless {
# other properties are skipped
subjects => \#subjects
}, $class;
}
sub add_subject{
my ($self,$args) = #_;
my $sub1 = $self->new_subject($args);
push #{ $self->{subjects} }, $sub1;
return $self;
}
Then you might iterate over the subjects freely:
print "subjects:\n";
foreach my $subj (#{ $et_stud->{subjects} }) {
print "subject_name: ", $subj->{subject_name} // '(none)', "\n",
"credit: ", $subj->{credit} // '(none)', "\n",
"grade: ", $subj->{grade} // '(none)', "\n";
}
Second thing. You're examining the last item in the #all_students array - $stud2 which has no subjects added.
You might want to check the number of the student subjects:
if (scalar #{ $et_stud->{subjects} }) {
# run the loop...
} else {
print "Student $et_stud->{student_name} has no subjects.\n";
}
(an array in scalar context returns the number of items.)

Inheritance in Perl

I have two classes, one is the base class(Employee) and the other its subclass which inherits it (Extend) the codes of which are shown below :
package Employee;
require Exporter;
our #EXPORT = ("getattr");
our $private = "This is a class level variable";
sub getattr {
print "Inside the getattr function of Employee.pm module \n";
$self = shift;
$attr = shift;
return ($self->{$attr});
}
1;
================
package Extend;
use base qw(Employee);
sub new {
print "Inside new method of Employee.pm \n";
my $class = shift;
my %data = (
name => shift,
age => shift,
);
bless \%data , $class;
}
sub test {
print "Inside test method of Extend class \n";
}
1;
==================
Now I have another piece of code which is using the Extend class :
use Extend;
my $obj = Extend->new("Subhayan",30);
my $value = $obj->getattr("age");
print ("The value of variable age is : $value \n");
$obj->test();
print "Do i get this : $obj.$private \n";
My question is regarding the variable $private defined in the parent class. As per my concept of inheritance attributes and methods of the parent class should be available through the subclass object . For example the function getattr runs fine . But why am I not being able to access $private variable using the base class Extend object .
What am I missing here ? Can someone please help ?
Variables don't get inherited the same way subs do. To access it, you need to specify the entire package name (and yes, when you declare $private, you need our, not my):
print "$Employee::private\n";
It's much more robust to define accessor methods:
# Employee package
sub private {
return $private;
}
...then in your script:
my $var = private();
To inherit object attributes from Employee, you can do:
# Employee
sub new {
my $self = bless {
dept => 'HR',
hours => '9-5',
}, shift;
return $self;
}
# Extend
sub new {
my $class = shift;
my $self = $class->SUPER::new; # attrs inherited from Employee
$self->{extended} = 1;
return $self;
}
# script
my $extend = Extend->new;
Now $extend looks like:
{
dept => 'HR',
hours => '9-5',
extended => 1,
}
You most likely wouldn't set dept or hours in the base class as it will apply to all employees, but I digress.

perl subroutine reference

I have a set of fields with each field having different set of validation rules.
I have placed the subroutine reference for validating a hash-ref.
Currently its in my constructor, but I want to take it out of my constructor in a private sub.
I have done it as below
sub new {
my $class = shift;
my $self = {#_};
$class = (ref($class)) ? ref $class : $class;
bless($self, $class);
$self->{Validations} = {
Field1 => {name => sub{$self->checkField1(#_);},args => [qw(a b c)]}
Field2 => {name => sub{$self->checkField2(#_);},args => {key1, val1}}
..
..
..
..
};
return $self;
}
Now I want to take out all this validation rules out of my constructor and want to do some thing like below, so that I have some better control over my validation rules based on types fields.(Say some rules are common in one set of fields and I can overwrite rules for other rules just by overwriting the values of fields.)
bless($self, $class);
$self->{Validations} = $self->_getValidation($self->{type});
return $self;
}
sub _getValidation{
my ($self,$type) = #_;
my $validation = {
Field1 => {name => sub {$self->checkField1(#_);}, args => {key1 => val1}},};
return $validation;
}
But I am getting Can't use string ("") as a subroutine ref while "strict refs" in use at... Can anybody tell me why is this behavior with sub ref. If I check my name key, its coming to be null or sub {DUMMY};
It looks to me like you are getting close to reinventing Moose poorly. Consider using Moose instead of building something similar, but less useful.
The error message means that you are passing in a string in a place where your code expects a code reference. Get a stack trace to figure out where the error is coming from.
You can do this by using Carp::Always, overriding the $SIG{__DIE__} handler to generate a stack trace, or inserting a Carp::confess into your code.
Here's a sigdie solution, stick this in your code where it will run before your module initialization:
$SIG{__DIE__} = sub { Carp::confess(#_) };
You may need to put it in a BEGIN block.
I'd really like to discourage you from taking this approach to building objects. You happily bless any random crap passed in to the constructor as part of your object! You blithely reach into your object internals. Field validation rules *do not belong in the constructor--they belong in the attribute mutators.
If you must use a DIY object, clean up your practices:
# Here's a bunch of validators.
# I set them up so that each attribute supports:
# Multiple validators per attribute
# Distinct error message per attribute
my %VALIDATORS = (
some_attribute => [
[ sub { 'foo1' }, 'Foo 1 is bad thing' ],
[ sub { 'foo2' }, 'Foo 2 is bad thing' ],
[ sub { 'foo3' }, 'Foo 3 is bad thing' ],
],
other_attribute => [ [ sub { 'bar' }, 'Bar is bad thing' ] ],
);
sub new {
my $class = shift; # Get the invocant
my %args = #_; # Get named arguments
# Do NOT make this a clone method as well
my $self = {};
bless $class, $self;
# Initialize the object;
for my $arg ( keys %args ) {
# Make sure we have a sane error message on a bad argument.
croak "Bogus argument $arg not allowed in $class\n"
unless $class->can( $arg );
$self->$arg( $args{$arg} );
}
return $self;
}
# Here's an example getter/setter method combined in one.
# You may prefer to separate get and set behavior.
sub some_attribute {
my $self = shift;
if( #_ ){
my $val = shift;
# Do any validation for the field
$_->[0]->($val) or croak $_->[1]
for #{ $VALIDATORS{some_attribute} || [] };
$self->{some_attribute} = $val;
}
return $self->{some_attribute};
}
All this code is very nice, but you have to repeat your attribute code for every attribute. This means a lot of error-prone boilerplate code. You can get around this issue by learning to use closures or string eval to dynamically create your methods, or you can use one of Perl's many class generation libraries such as Class::Accessor, Class::Struct, Accessor::Tiny and so forth.
Or you can learn [Moose][3]. Moose is the new(ish) object library that has been taking over Perl OOP practice. It provides a powerful set of features and dramatically reduces boilerplate over classical Perl OOP:
use Moose;
type 'Foo'
=> as 'Int'
=> where {
$_ > 23 and $_ < 42
}
=> message 'Monkeys flew out my butt';
has 'some_attribute' => (
is => 'rw',
isa => 'Foo',
);
I haven't read everything you had, but this struck me:
sub new {
my $class = shift;
my $self = {#_};
$class = (ref($class)) ? ref $class : $class;
bless($self, $class);
Normally, when you create a new object, the user doesn't pass $self as one of the objects. That's what you're creating.
You usually see something like this:
sub new {
my $class = shift; #Contains the class
my %params = #_; #What other parameters used
my $self = {}; #You're creating the $self object as a reference to something
foreach my $param (keys (%params)) {
$self->{$param} = $params{$param};
}
bless ($self, $class) #Class is provided. You don't have to check for it.
return $self #This is the object you created.
}
Now, $self doesn't have to be a reference to a hash as in the above example. It could be a reference to an array. Or maybe to a function. But, it's usually a reference. The main point, is that the user doesn't pass in $self because that's getting created by your new subroutine.
Nor, do you have to check the value of $class since that's given when the new subroutine is called.
If you want to do your verification in a private class (an excellent idea, by the way), you can do so after the bless:
sub new {
my $class = shift; #Contains the class
my %params = #_; #What other parameters used
my $self = {}; #You're creating the $self object as a reference to something
foreach my $param (keys (%params)) {
$self->{$param} = $params{$param};
}
bless ($self, $class) #Class is provided. You don't have to check for it.
#Now you can run your verifications since you've blessed the object created
if (not $self->_validate_parameters()) {
croak qq(Invalid parameters passed in class $class);
}
return $self #This is the object you created.
}