Accessor with type constraint
In this part we are adding a new attribute with an accessor that checks if the value given is in the correct type.
The next thing we did in the example with Moose earlier, we created another attribute which has some type. We used it this way.
examples/Moose/person02/script/person.pl
use strict; use warnings; use v5.10; use Person; my $student = Person->new( name => 'Foo' ); $student->year(1988); say $student->year; $student->year('23 years ago');
We had an attribute called "year" that would accept a number (an integer representing a year) such as 1988, but would not accept a string such as '23 years ago'.
The implementation was very simple. We just declared that we have an attribute called "year" and we used the "isa" keyword to tell Moose that this should only accept Integers.
examples/Moose/person02/lib/Person.pm
package Person; use Moose; has 'name' => (is => 'rw'); has 'year' => (isa => 'Int', is => 'rw'); 1;
We would get a detailed exception if we passed a value that was not an Int.
examples/Moose/person02/err.txt
Attribute (year) does not pass the type constraint because: Validation failed for 'Int' with value "23 years ago" at accessor Person::year (defined at lib/Person.pm line 5) line 4 Person::year('Person=HASH(0x19a4120)', '23 years ago') called at script/person.pl line 13
Attribute constraint in Core Perl OOP
So how can we implement the same thing using Object Oriented Programming using Core Perl only?
examples/oop/person02/lib/Person.pm
package Person; use strict; use warnings; sub new { my ($class, %args) = @_; my $self = \%args; bless $self, $class; return $self; } sub name { my ($self, $value) = @_; if (@_ == 2) { $self->{name} = $value; } return $self->{name}; } sub year { my ($self, $value) = @_; if (@_ == 2) { die qq{Attribute (year) does not pass the type constraint because: } . qq{Validation failed for 'Int' with value "$value"} if $value !~ /^\d+$/; $self->{year} = $value; } return $self->{year}; } 1;
The constructor (the new function) has not changed.
We have included an additional subroutine called year to handle the year attribute. This is almost exactly the same as the earlier getter/setter called name, except that this one has a conditional call to die and that the name of the subroutine that corresponds to the name of the attribute is now 'year' instead of 'name'.
If we did not want to add the extra "die" call, we could have just copy-pasted the previous accessor and replace the word 'name' by the word 'year' in 3 places.
Because we wanted to have some validation in the second accessor, we also had to add a call to 'die'; I've added a copy of the error message we saw in the Moose case which is quite long. If you really write core Perl OOP, you'd probably write much shorter error messages.
Moreover, as far I know, most of the people who write OOP code using core Perl only, would leave out this simple error checking, but I wanted the error message to look exactly the same as the one Moose provides.
We call die only if the regex at the end of the statement does not match. I am not sure if the regex /^\d+$/ checks exactly the same as Moose checks with the Int declaration, but I wanted to have some generic example. (Actually, most likely it is closer to this /^[+-]?\d+$/.)
Use Carp::croak
After recording the video I thought that instead of calling die we would probably be better off calling Carp::croak as that would indicate the failure being at the point where the setter was called and not inside the setter.
Published on 2018-07-14