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

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.

Related

Using sub reference with Moose builder

Moose documentation mentions that builder property for attribute in class definiton should be a string that contains name for function that will be called to build relevant attribute. Simple testing however shows that sub reference works as well:
has 'some_attribute' => (
is => 'ro',
lazy => 1,
builder => sub {
require SomeModule::Heavy;
return SomeModule::Heavy->new($_[0]);
},
);
Did I miss something in docs? Is usage of sub reference officially supported for builder?
Moose manual says:
You can also provide a subroutine reference for default. This reference will be called as a method on the object. […] As an alternative to using a subroutine reference, you can supply a builder method for your attribute. This has several advantages. First, it moves a chunk of code to its own named method, which improves readability and code organization. Second, because this is a named method, it can be subclassed or provided by a role.
So, if you use subroutine reference for builder then you lose these advantages. I think subroutine reference works as side effect and has no practical application.

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.

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)

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

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);