How can I acces read-only attributes of Moose objects? - perl

I'm an absolute newbie to Moose and so far I have read Moose and most of the Cookbook.
There a few things I don't get. I created the following package:
package MyRange;
use Moose;
use namespace::autoclean;
has [ 'start', 'end' ] => (
is => 'ro',
isa => 'Int',
required => 1,
);
__PACKAGE__->meta->make_immutable;
1;
Then:
use MyRange;
my $br = MyRange->new(
start => 100,
end => 180
);
Now I can access my fields using e.g. $br->{start}, but I can also modify them (although they are "read only") using e.g. $br->{start}=5000. I can also add new keys like $br->{xxx}=111.
Am I missing anything? Isn't the object protected in some way? What's the meaning of ro?

When you said is => 'ro' you told Moose to create read-only accessors for you, that is, a reader method. You call that as
$br->start;
or
$br->end;
Setting the attributes using those methods will result in an exception:
$br->start(42);
If you had used is => 'rw', then the above would work and update the attribute's value.
What you're doing is direct hash access on the object, which violates encapsulation and shouldn't ever be necessary when using Moose.
The Moose manual, i.e. all the documents under the Moose::Manual namespace explain that in detail. A good starting point for questions like this is probably Moose::Manual::Attributes.

When you access to the attribute with $br->{start}, you are bypassing the accessor and you are adressing directly the underlying Moose implementation. You can do it, but you are not supposed to. Also, if Moose changes the implementation, your code will break.
You should instead access the attribute using the accessor method:
my $start = $br->start;
When you say that the attribute is 'RO', it means you are not allowed to change the attribute value using the accessor:
$br->start(32);

Related

Moose attribute default used even though subclass overrides the attribute

I'm tinkering with Moose as introduced in Intermediate Perl. I have an abstract class Animal with a property sound. The default behaviour should be to complain that sound has to be defined in subclasses:
package Animal;
use namespace::autoclean;
use Moose;
has 'sound' => (
is => 'ro',
default => sub {
confess shift, " needs to define sound!"
}
);
1;
A subclass has to do nothing else than define sound:
package Horse;
use namespace::autoclean;
use Moose;
extends 'Animal';
sub sound { 'neigh' }
1;
But testing this with
use strict;
use warnings;
use 5.010;
use Horse;
my $talking = Horse->new;
say "The horse says ", $talking->sound, '.';
results in
Horse=HASH(0x3029d30) needs to define sound!
If I replace the anonymous function in Animal with something simpler as in
has 'sound' => (
is => 'ro',
default => 'something generic',
);
things work fine. Why is that? Why is the default function executed even though I override it in the subclass?
There's two things in play here: How attributes are initialized and how accessors work.
Non-lazy ('eager') attributes are initialized when the class is instantiated. That's why you can actually leave off the
say "The horse says ", $talking->sound, '.';
and get the same error. If you make the attribute lazy, on the other hand, the error goes away. And that leads us to the real reason: the difference between attributes, accessors, and builders.
Animal has an attribute, sound, which is just a place that stores some data related to instances of the class. Because sound was declared ro, Animal also has a method that acts as an accessor, confusingly also called sound. If you call this accessor, it looks at the value of the attribute and gives it to you.
But this value exists independent of the accessor. The accessor provides a way to get at the value, but the actual existence of the value is dependent on the attribute's builder. In this case, the builder for the attribute is the anonymous method sub { confess shift, " needs to define sound!" }, and it will get run as soon as the attribute needs to have a value.
In fact, if you leave out the is => 'ro', you will stop Moose from creating an accessor at all, and the error will still pop at construction time. Because that's when your class builds the sound attribute.
When the attribute needs its value depends on whether you've declared it as lazy or not. Eager attributes are given their values on object construction. It doesn't matter if there's an accessor, the builder gets called when the object is created. And in this case, the builder dies.
Lazy attributes are given their values the first time they are needed. The default accessor tries to get the value of the attribute, which causes the builder to fire, which causes the script to die. When you override sound, you replace the default accessor with one that doesn't call the builder and therefore doesn't die anymore.
Does that mean you should make the sound attribute lazy? No, I don't think so. There's better mechanisms available, depending on what exactly you are trying to assert. If what you are trying to assert is that Animal->sound must be defined, you can use BUILD like so:
package Animal;
use namespace::autoclean;
use Moose;
has 'sound' => (is => 'ro');
sub BUILD {
my ($self) = #_;
confess "$self needs to define sound!"
unless defined $self->sound;
}
1;
During object construction, each of the parent classes' BUILD methods gets called, which lets them make assertions about object state.
If, on the other hand, what you wanted to assert is that a subclass has to have overridden sound, it's better not to make sound an attribute at all. Instead,
package Animal;
use namespace::autoclean;
use Moose;
sub sound {
confess "Abstract method `sound` called!";
}
1;

Changing Attribute values in an immutable class using Moose

I am fairly new to Moose, and have been trying to follow the best practices. I understand that we should try to make classes immutable when we can. My question is, after object construction, can we change the value of a public attribute using a setter and still have the class be immutable?
Yes. If a class is immutable, this only means we cannot add new attributes or new methods to the class. This allows the Moose system do do some neat optimizations.
Any instances of the class can still be mutated. In order for the instances to be immutable as well, all attributes must be readonly (is => 'ro').
Example:
package MyClass;
use Moose;
has attr => (is => 'rw'); # this attribute is read-write
__PACKAGE__->meta->make_immutable;
# after this, no new attributes can be added
Then:
my $instance = MyClass->new(attr => "foo");
say $instance->attr; # foo
$instance->attr("bar"); # here, we change the instance, but not the class
say $instance->attr; # bar

How do I create a custom write accessor when using Moose?

Edit: Answer added below, question left here for historical purposes only.
Moose documentation states that:
If you want, you can also explicitly specify the method names to be
used for reading and writing an attribute's value. This is
particularly handy when you'd like an attribute to be publicly
readable, but only privately settable. For example:
has 'weight' => (
is => 'ro',
writer => '_set_weight',
);
This might be useful if weight is calculated based on other methods.
For example, every time the eat method is called, we might adjust
weight. This lets us hide the implementation details of weight
changes, but still provide the weight value to users of the class.
Based on this example, I wrote the following code:
has '_current_url' => (
is => 'ro',
isa => 'URI',
writer => '_write_URI_to_current_url',
);
# Thus we ensure only a URI object gets written to current_url always
sub _current_url
{
my $self = shift;
$self->_write_URI_to_current_url(URI->new_abs($_[0], $self->start_url));
}
My intention was to ensure that setting current_url always sets it to a URI object even if it was called with a simple string. However, when I try to run the code, I get:
Cannot assign a value to a read-only accessor of _current_url
at the place (within my class) where I'm trying to set the current_url (with $self->_current_url($str);).
This is my second day with Moose so I'm quite confused as to what's going on here. To my understanding the is => 'ro' only asks Moose not to create a default write accessor with the same name as the attribute, is that understanding correct? How can I achieve my goal here?
Ok, I believe I've found the issue.
The _current_url method I've pasted above got overridden by Moose's generated read-only accessor of the same name, so when I tried to call $self->_current_url with a value, it throws the above error to indicate that the read-only accessor cannot set values.
I guess the error message should ideally be Cannot assign a value **through** a read-only accessor of _current_url, not **to** a read-only accessor.
Changing the name of the sub to _set_current_url solved the problem. I guess another way to achieve the same would be to tell Moose that _current_url is => 'rw' and then create an around '_current_url'. I haven't tried this approach.
Late to the game but just saw this post and ran into the same thing a while back. '_set_current_url' really seems like an attribute accessor instead of an attribute. Might want to consider:
has '_current_url' => {
is => 'rw',
isa => 'URI',
writer => 'set_current_url',
reader => 'get_current_url'
}
A bit cleaner this way since the attribute is the original '_current_url' and you have accessors to get/set the attribute.

How can you create private/public variable and functions using Moose?

I am going through the Moose recipes and I still cannot see if I can create private variables and functions using it? Is it possible? If yes how to create them with Moose?
Like daxim points out, private methods have the "_" prefix. Because attributes (instance variables) generate getters methods (and if rw also setters methods) out of the box, you should do this:
has 'myvariable' => (
is => 'ro',
writer => '_myvariable',
init_arg => undef,
# other options here
);
This way you can set this attribute within your class/instance and it's not settable from outside. If read-only access is too much, you can also mark it "private":
has '_myvariable' => (
is => 'ro',
writer => '_set_myvariable'
init_arg => undef,
# other options here
);
Prefix an identifier with an _ to mark the function/variable etc. as private. This is documented in perlstyle in the section about scope, about in the middle of the document.
This is respected by sane programmers and some tools (source parsers/documentation), but not enforced by the compiler. See perlmodlib#NOTE.

Perl Moose parent class cast with child

package Point;
use Moose;
has 'x' => (isa => 'Int', is => 'rw');
has 'y' => (isa => 'Int', is => 'rw');
package Point3D;
use Moose;
extends 'Point';
has 'z' => (isa => 'Int', is => 'rw');
package main;
use Data::Dumper;
my $point1 = Point->new(x => 5, y => 7);
my $point3d = Point3D->new(z => -5);
$point3d = $point1;
print Dumper($point3d);
Is it possible to cast a parent to child class such as c++? In my examble is $point3d now a Point and not a Point3D include the Point.
Take a look at the Class::MOP documentation on CPAN, especially the clone_object and rebless_instance methods:
sub to_3d {
my ($self, %args) = #_;
return Point3D->meta->rebless_instance(
$self->meta->clone_object($self),
%args,
);
}
And then use it like the following:
my $point_3d = $point->to_3d(z => 7);
This will also take care to treat the newly specified %args as if they've been passed in by the constructor. E.g. builders, defaults, and type constraints are all considered during this build.
You do not need to cast in Perl, since it is a dynamic language. In your case though, the variable $point3d contains a reference to a Point object at the end of the script. You cannot treat this as a Point3D because it is not a Point3D. You could manually convert it, but you can't "remake" an existing object as a different class. (Well, you theoretically can in Perl, but you shouldn't.)
Well, Dumper should tell you that $point3d is now a Point, not a Point3D, because your assignment of $point3d = $point1 makes $point3d a second reference to the same object as $point1. The Point3D instance that was originally referenced by $point3d is now lost in space with a refcount of 0, making it eligible for garbage collection.
As cdhowie said, you don't really do typecasting in Perl the way you do in C/C++. The closest I can think of is to fall back on the non-OO calling convention and use, e.g., Point3D::z($point1, 4) to give $point1 a z-index of 4, but that's kind of clumsy and you'd have to use the same syntax for any future references to it's z-index. Note also that, when using this calling convention, Point3D must actually define a z method[1] or you'll get a runtime error because inheritance doesn't work when you do it this way because you're referring to Point3D as a package, not to $point1 as an object.
If you want to actually make your Point into a Point3D, you can easily change the actual type of an object using bless (the same command used to change a plain reference into an object in the first place, although that's hidden inside of Moose in your example code), but I suspect that manually reblessing a Moose object would anger the Moose. (But, if that is the case, I'm sure Moose provides a safer way of changing an object's class. I just don't use Moose enough to know what it would be.)
[1] ...or AUTOLOAD, but that's an entirely different can of worms.
Basically, I am seconding what cdhowie and Dave S. said, but one more thing to add.
If you DO want to make $point3d which holds an object of class Point into a real object of subclass Point3D, the correct OO way of doing it is by creating a constructor new_from_Point() in Point3D class, which takes an object of class Point as input and creates an Point3D object (it should probably take an extra "z" parameter). The C++ equivalent would be a constructor with a signature of (const Point &, double &z=0.0)