I am starting to learn about objects in Perl using Moose.
I am not sure if I understand the purpose of MooseX::Privacy. Consider:
use v5.14;
package PA {
use Moose;
my $var='private?';
1;
sub getVar {
return $var;
}
}
package PB {
use Moose;
use MooseX::Privacy;
has 'var' => (
is => 'rw',
isa => 'Str',
default => 'private?',
traits => [qw/Private/],
);
1;
sub getVar {
my $self = shift;
return $self->var;
}
}
my $o1= PA->new();
my $o2= PB->new();
say $o1->getVar();
say $o2->getVar();
In both class PA and PB I have a private variable var. Only in class PB I use MooseX::Privacy. What is the difference between these two approaches? And why should I use MooseX::Privacy?
If you're looking for Java-style method privacy, then MooseX::Privacy is going to be a big disappointment. Here's what happens with Java style method privacy:
/* This file is called Main.java */
public class Main
{
public class MyParent
{
private String message_string ()
{
return "Message from %s\n";
}
public void print_message ()
{
System.out.printf( this.message_string(), "MyParent" );
}
}
public class MyChild extends MyParent
{
public String message_string ()
{
return "Another message from %s\n";
}
}
public static void main (String[] args)
{
Main o = new Main();
o.run();
}
public void run ()
{
MyParent c = new MyChild();
c.print_message();
}
}
You can compile and run this example like this:
$ javac Main.java
$ java Main
Message from MyParent
Note what's happened. The parent class (MyParent) declares message_string() to be a private method. The child class attempts to override the method but is roundly rebuffed - no soup for you child class!
Now let's try the equivalent with Perl and MooseX::Privacy...
# This file is called Main.pl
use v5.14;
use strict;
use warnings;
package MyParent {
use Moose;
use MooseX::Privacy;
private_method message_string => sub {
my $self = shift;
return "Message from %s\n";
};
sub print_message {
my $self = shift;
printf($self->message_string(), __PACKAGE__);
}
}
package MyChild {
use Moose; extends qw(MyParent);
use MooseX::Privacy;
sub message_string {
my $self = shift;
return "Another message from %s\n";
}
}
my $c = new MyChild();
$c->print_message();
We can run that like this:
$ perl Main.pl
Another message from MyParent
Say, WHA?!?!?! Ain't message_string supposed to be private?! How the hell did MyChild override the method in MyParent?!
The fact of the matter is, MooseX::Privacy doesn't give you anything close to method privacy as implemented in most OO languages. MooseX::Privacy is simply akin to doing this in your method:
die "GO AWAY!!" unless caller eq __PACKAGE__;
Except that MooseX::Privacy adds massive runtime expense to all your method calls.
Really, there's little reason to use MooseX::Privacy. If you want private methods, put them in lexical variables. Like this:
use v5.14;
use strict;
use warnings;
package MyParent {
use Moose;
my $message_string = sub {
my $self = shift;
return "Message from %s\n";
};
sub print_message {
my $self = shift;
printf($self->$message_string(), __PACKAGE__);
}
}
package MyChild {
use Moose; extends qw(MyParent);
sub message_string {
my $self = shift;
return "Another message from %s\n";
}
}
my $c = new MyChild();
$c->print_message();
Now run it:
$ perl Main2.pl
Message from MyParent
Hallelujah!! We have a true private method!
OK, so you can have private methods without MooseX::Privacy, and they work better (and faster) than MooseX::Privacy.
But what about private attributes? Well, I have a little module on CPAN that can help you: Lexical::Accessor. This is a little tool that creates an attribute for you, with "inside out" storage (i.e. the attribute value doesn't get stored in the object's blessed hashref), and installs the accessors for it in lexical variables (just like the private $get_message method above).
Anyway, that's my opinion on MooseX::Privacy.
Related
In Python you can do:
class Binance(Exchange):
name = "Binance"
code = "binance"
and in the parent class have
class Exchange:
#classmethod
def get_name(cls):
return cls.name
Now Perl!
This is lovely. I want the same for my Perl objects.
package DWDESReader;
use base qw(DWConfigFileReader);
our $type = "DES";
and in the base class:
package DWConfigFileReader;
our $type = "";
sub new {
my ($class, %args) = #_;
$args{type} = $type;
return bless {%args}, $class;
}
sub getType {
my ($self) = #_;
return $self->{type};
}
But this doesn't work, i.e. only returns the empty string assigned in the base class. I didn't expect it to work but am unsure how it should be done.
I don't see why one should need it, but it's possible, if you turn off strict refs:
#!/usr/bin/perl
use warnings;
use strict;
{ package My::Base;
sub new { bless {}, shift }
our $name = 'Base';
sub get_name {
my ($self) = #_;
my $class = ref $self || $self;
do { no strict 'refs';
${ $class . '::name' }
}
}
}
{ package My::Child;
use parent -norequire => 'My::Base';
our $name = 'Child';
}
my $ch = 'My::Child'->new;
print $ch->get_name, ' ', 'My::Child'->get_name;
But usually, you would just define a class method holding the name:
{ package My::Base;
sub new { bless {}, shift }
sub name { 'Base' }
sub get_name { shift->name }
}
{ package My::Child;
use parent -norequire => 'My::Base';
sub name { 'Child' }
}
Classes don't have attributes (variables) in Perl, only methods (subs).
I recommend creating an abstract virtual class method.
package DWConfigFileReader;
use Carp qw( croak );
sub new {
my ($class, %args) = #_;
my $self = bless(\%args, $class);
return $self;
}
sub type { croak("Subclass must override \"type\"."); }
1;
package DWDESReader;
use parent 'DWConfigFileReader';
sub type { "DES" }
1;
You don't even need $self->{type} = $class->type;; just use $self->type instead of $self->{type}.
As has been suggested, Perl inherits methods (subs), not variables, but constants are actually subs, so you can do something similar like this.
package DWDESReader;
use base qw(DWConfigFileReader);
use constant TYPE => "DES";
Then, if you call $self->TYPE somewhere in the base class, you'll get "DES" if the object is actually a DWDESReader object.
I have a class X with a subclass Y. X has a method calculate() that I'd like to override in Y with some additional behaviour, an if statement that, if it fails, call X.calculate(). In Python this would be accomplished with:
class X(object):
def calculate(self, my_arg):
return "Hello!"
class Y(X):
def calculate(self, my_arg):
if type(my_arg) is int and my_arg > 5:
return "Goodbye!"
return super(Y, self).calculate(my_arg)
How can I do this in Perl using the Moo module?
As the docs point out:
No support for super, override, inner, or augment - the author considers augment to be a bad idea, and override can be translated:
around foo => sub {
my ($orig, $self) = (shift, shift);
...
$self->$orig(#_);
...
};
(emphasis mine)
#!/usr/bin/env perl
use strict;
use warnings;
package X;
use Moo;
sub calculate {
return 'Hello!'
}
package Y;
use Moo;
extends 'X';
around calculate => sub {
my $orig = shift;
my $self = shift;
if ( $_[0] > 5 ) {
return $self->$orig(#_);
}
return 'Goodbye!';
};
package main;
my $y = Y->new;
print $y->calculate(3), "\n";
print $y->calculate(11), "\n";
This can be done in Perl via the SUPER:: pseudo-class, which is part of Perl's method resolution system. You just put it in front of the method-call. It does not work for class methods or function calls.
use strict;
use warnings;
use feature 'say';
package Foo;
use Moo;
sub frobnicate {
my $self = shift;
say "foo";
}
package Bar;
use Moo;
extends 'Foo';
sub frobnicate {
my $self = shift;
say "bar";
$self->SUPER::frobnicate;
}
package main;
Bar->new->frobnicate;
You can even use this to call each parent's method if you have multi-level inheritance.
package Grandparent;
sub foo { ... }
package Parent;
use parent 'Grandparent';
sub foo { $_[0]->SUPER::foo }
package Child;
use parent 'Parent';
sub foo { $_[0]->SUPER::foo }
This will subsequently call foo in Child, Parent and Grandparent.
I am dealing with the following problem. I am newbie in Perl.
The idea is that I have a class, it has array as member/field (in hash).
Let's name this class ProductContainer.
I have another class Product. These Classes are in separate files (separate modules). So I need to just add a Product object into ProductContainer object (internal array).
Simple example what I need in Java.
public class ProductContainer {
private List<Product> mProductsList;
public addProduct(Product product) {
this.mProductList.add(product);
}
}
It's not clear what exactly you are asking, as you haven't shown any Perl code.
If you are using bless for your objects, the following shows you how to do that. I put all the packages in the same file. Also, note that the add method checks the type of its argument.
#!/usr/bin/perl
use warnings;
use strict;
{ package ProductContainer;
use Carp;
sub new { bless {}, shift }
sub add {
my $self = shift;
my $product = shift;
croak "Invalid member" unless UNIVERSAL::DOES($product, 'Product');
push #{ $self->{mProductsList} }, $product;
}
}
{ package Product;
sub new {
my $class = shift;
bless { name => shift }, $class
}
}
my $p = 'Product'->new('Product 1');
my $pc = 'ProductContainer'->new;
$pc->add($p);
$pc->add('Product 2'); # Exception!
I'm trying to implement a Moose::Role class that behaves like an abstract class would in Java. I'd like to implement some methods in the Role, but then have the ability to override those methods in concrete classes. If I try this using the same style that works when I extend classes I get the error Cannot add an override method if a local method is already present. Here's an example:
My abstract class:
package AbstractClass;
use Moose::Role;
sub my_ac_sub {
my $self = shift;
print "In AbstractClass!\n";
return;
}
1;
My concrete class:
package Class;
use Moose;
with 'AbstractClass';
override 'my_ac_sub' => sub {
my $self = shift;
super;
print "In Class!\n";
return;
};
__PACKAGE__->meta->make_immutable;
1;
And then:
use Class;
my $class = Class->new;
$class->my_ac_sub;
Am I doing something wrong? Is what I'm trying to accomplish supposed to be done a different way? Is what I'm trying to do not supposed to be done at all?
Turns out I was using it incorrectly. I opened a ticket and was shown the correct way of doing this:
package Class;
use Moose;
with 'AbstractClass';
around 'my_ac_sub' => sub {
my $next = shift;
my $self = shift;
$self->$next();
print "In Class!\n";
return;
};
__PACKAGE__->meta->make_immutable;
1;
Making this change has the desired effect.
Some time ago, I did this by having a role that consists solely of requires statements. That forms the abstract base class. Then, you can put your default implementations in another class and inherit from that:
#!/usr/bin/env perl
use 5.014;
package AbstractClass;
use Moose::Role;
requires 'my_virtual_method_this';
requires 'my_virtual_method_that';
package DefaultImpl;
use Moose;
with 'AbstractClass';
sub my_virtual_method_this {
say 'this';
}
sub my_virtual_method_that {
say 'that'
}
package MyImpl;
use Moose;
extends 'DefaultImpl';
with 'AbstractClass';
override my_virtual_method_that => sub {
super;
say '... and the other';
};
package main;
my $x = MyImpl->new;
$x->my_virtual_method_this;
$x->my_virtual_method_that;
If you want to provide default implementations for only a few methods define in the role, remove the requires from DefaultImpl.
Output:
$ ./zpx.pl
this
that
... and the other
While refactoring I'm trying to retain some backwards compatibility for a time. I'm wondering if it's possible to have a method on an object, but prevent that method from being inherited by classes that subclass it? e.g. given
package Class {
use Moose;
sub foo { 'test' };
}
my $class = Class->new;
$class->foo;
would work, but
package Extended::Class {
use Moose;
extends 'Class';
}
my $class = Extended::Class->new;
$class->foo;
would not.
I realize this probably breaks some principle or another, but I'm deprecating these interfaces as I go.
How about:
use 5.014;
package Class {
use Carp qw( croak );
use Moose;
sub foo {
my $self = shift;
croak unless __PACKAGE__ eq ref $self;
return 'test';
}
}
package Extended::Class {
use Moose;
extends 'Class';
}
package main {
my $x = Class->new;
say $x->foo;
my $y = Extended::Class->new;
say $y->foo;
}
Have you considered delegation?
package Original {
use Moose;
sub foo { 23 }
sub bar { 42 }
}
package Subclass {
use Moose;
has original => (
buidler => '_build_original',
handles => [qw( bar )],
);
sub _build_original { Original->new }
}
Of course it depends on your situation if you can use it. The subclass won't pass isa checks for the above (but you can override isa if you must). Also passing the original arguments on to the object you're extending can be annoying depending on the use case.
Since it would look for the method foo in the Extended::Class first, you could just declare one there that doesn't do anything. That way the inherited one would not be called unless you do so somewhere in your subclass.
I'm not sure if Moose alters that behaviour, though.
package Class {
use Moose;
sub foo { 'test' }
}
package Extended::Class {
use Moose;
extends 'Class';
sub foo {
# do nothing
}
}
package main {
my $x = Class->new;
my $y = Extended::Class->new;
print $x->foo;
print $y->foo;
}