Perl - Overloading package/class properties? - perl

I am attempting to port some code from PHP that basically boils down to property overloading. That is, if you try to get or set a class property that is not actually defined as a part of the class, it will send that information to a function that will pretty much do anything I want with it. (In this case, I want to search an associative array within the class before giving up.)
However, Perl is... quite a bit different from PHP, given that classes are already hashes. Is there any way that I can apply some equivalent of __get() and __set() to a Perl "class" that will remain completely encapsulated in that package, transparent to anything trying to actually get or set properties?
EDIT: The best way to explain this may be to show you code, show the output, and then show what I want it to output.
package AccessTest;
my $test = new Sammich; #"improper" style, don't care, not part of the question.
say 'bacon is: ' . $test->{'bacon'};
say 'cheese is: ' . $test->{'cheese'};
for (keys $test->{'moreProperties'}) {
say "$_ => " . $test->{'moreProperties'}{$_};
}
say 'invalid is: ' . $test->{'invalid'};
say 'Setting invalid.';
$test->{'invalid'} = 'true';
say 'invalid is now: ' . $test->{'invalid'};
for (keys $test->{'moreProperties'}) {
say "$_ => " . $test->{'moreProperties'}{$_};
}
package Sammich;
sub new
{
my $className = shift;
my $this = {
'bacon' => 'yes',
'moreProperties' => {
'cheese' => 'maybe',
'ham' => 'no'
}
};
return bless($this, $className);
}
This currently outputs:
bacon is: yes
Use of uninitialized value in concatenation (.) or string at ./AccessTest.pl line 11.
cheese is:
cheese => maybe
ham => no
Use of uninitialized value in concatenation (.) or string at ./AccessTest.pl line 17.
invalid is:
Setting invalid.
invalid is now: true
cheese => maybe
ham => no
Now, I need to make modifications to Sammich only, without making any changes at all to the initial AccessTest package, that will result in this:
bacon is: yes
cheese is: maybe
cheese => maybe
ham => no
invalid is: 0
Setting invalid.
invalid is now: true
cheese => maybe
ham => no
invalid => true
As you can see, the desired effect is that the 'cheese' property, since it's not a part of the test object directly, would instead be grabbed from the 'moreProperties' hash. 'invalid' would attempt the same thing, but since it is neither a direct property nor in 'moreProperties', it would act in whatever way programmed - in this case, I would want it to simply return the value 0, without any errors or warnings. Upon attempting to set the 'invalid' property, it would not be added to the object directly, because it's not already there, but would instead be added to the 'moreProperties' hash.
I'm expecting this to take more than the six or so lines it would require in PHP, but as it is a very important concept of OOP, I fully expect Perl to handle it somehow.

As I have said in my comments, the reason this problem is hard is that you aren't following one of the golden rules of Object-Oriented Programming, namely encapsulation. How can you expect to intercept a call that isn't a method? If your exposed API consists of getter/setters then you can intercept an unknown method call with an AUTOLOAD method. If you don't you may use #pilcrow's noble suggestion of using a tied hash (Edit: or #tchrist's valiant use of the overload pragma); still this is more a tribute to Perl's flexibility than your API.
To do this more correctly (and yes I see you "require" that the API not be modified, if you choose to ignore this, then call this post a message to future readers).
#!/usr/bin/env perl
use v5.10; # say
use strict;
use warnings;
use MooseX::Declare;
use Method::Signatures::Modifiers;
class Sammich {
has 'bacon' => ( isa => 'Str', is => 'rw', default => 'yes' );
has 'more' => (
isa => 'HashRef',
is => 'rw',
default => sub{ {
cheese => 'maybe',
ham => 'no',
} },
);
our $AUTOLOAD;
method AUTOLOAD ($val?) {
# get unknown method name
my $attr = (split( /::/, $AUTOLOAD))[-1];
my $more = $self->more;
# see if that method name exists in "more"
if (exists $more->{$attr}) {
# if so, are there new values? then set
if (defined $val) {
$more->{$attr} = $val;
}
# ... and return
return $more->{$attr};
}
# attr does not exist, so set it or initialize it to 0
$more->{$attr} = defined $val ? $val : 0;
return $more->{$attr};
}
}
# I don't care that you don't care
my $test = Sammich->new();
say 'bacon is: ' . $test->bacon;
say 'cheese is: ' . $test->cheese;
for (keys %{ $test->more }) {
say "$_ => " . $test->more->{$_};
}
say 'invalid is: ' . $test->invalid;
say 'Setting invalid.';
$test->invalid( 'true' );
say 'invalid is now: ' . $test->invalid;
for (keys %{ $test->more }) {
say "$_ => " . $test->more->{$_};
}
Some may say that my wording here is harsh and perhaps it is. I try to help those who will be helped, therefore seeing a bolded message like
I need to make modifications to Sammich only, without making any changes at all to the initial AccessTest package
then demanding that Perl bow to your whim
I'm expecting this to take more than the six or so lines it would require in PHP, but as it is a very important concept of OOP, I fully expect Perl to handle it somehow.
is irksome. I hope that future readers will see this as a case example of why encapsulation helps in the long run.

Update to the update
(I am receiving anonymous downvotes, presumably for abetting your misguided approach. :) )
Just to be clear, the question you pose is an "XY Problem", specifically an artifact of the mistaken translation of OO technique from PHP to Perl. For example, as mentioned passim in this question, object properties in perl generally should not be implemented as directly accessed hash(ref) elements. That's the wrong "X".
Jumping from one language to another introduces more than merely syntactical differences.
Update
You can accomplish what you want with something like this:
package UnfortunateHack; {
use Tie::Hash;
our #ISA = qw(Tie::StdHash);
sub FETCH {
my ($self, $key) = #_;
return exists $self->{$key}
? $self->{$key}
: $self->{moreProperties}->{$key};
}
}
...
package Sammich;
sub new {
my $class = shift;
tie my %this, 'UnfortunateHack';
%this = ( bacon => 'yes',
moreProperties => { ... } );
bless \%this, $class;
}
Original Answer
If I understand your intent — to intercept $obj->property calls where TheClass::property isn't necessarily defined — then perl's AUTOLOAD in an OO context will do what you want.
From the linked docs (perlobj):
If you call a method that doesn't exist in a class, Perl will throw an error. However, if that class or any of its parent classes defines an AUTOLOAD method, that AUTOLOAD method is called instead.

You are completely violating encapsulation. To prove it to you, comment out the bless from &Sammich::new so that it returns a plain hash reference.
package Sammich;
sub new {
my $className = shift;
my $this = {
'bacon' => 'yes',
'moreProperties' => {
'cheese' => 'maybe',
'ham' => 'no'
}
};
# don't even bother blessing it
# return bless($this, $className);
}
The only way to get what you want it is to use magic.

In Perl classes are more than hashes, they are built on packages and you can define there whatever method you want and it remains encapsulated in that package/class.
You can see a code example in the Object-Oriented Programming in Perl Tutorial.

Related

why can't I run this perl code?

While following this tutorial
https://www.codeproject.com/Articles/3152/Perl-Object-Oriented-Programming
I am failing to see where module Address.pm is.. did I miss something or article has an error or do I have a misunderstanding when one of the module says ' use Address ';
mac1:moduleTEST1 user1$ ./Employee.pl
Can't locate object method "new" via package "Address" (perhaps you forgot to load "Address"?) at ./Employee.pl line 16.
mac1:moduleTEST1 user1$
The tutorial is outdated and rather useless. Specifically, it is much worse than the documentation which comes with Perl. Use perldoc perltoc to get a table of contents, and read everything at least once.
See perldoc perlootut and perldoc perlobj.
package Address;
use strict;
use warnings;
sub new {
my $class = shift;
my $args = shift;
my %self = map +($_ => $args->{$_}), qw( street city state zip );
bless \%self => $class;
}
sub street {
my $self = shift;
if ( #_ ) {
$self->{street} = $_[0];
return;
}
return $self->{street};
}
# ditto for the rest of the accessors # there are
# ways to cut down the boilerplate once you learn
# the basics
#
# ...
__PACKAGE__
__END__
You use this module like this:
my $address = Address->new({
street => '123 E. Any St',
city => 'Any Town',
state => 'AY',
zip => '98765',
});
Of course, there a lot of things missing from this little demo. For example, the accessor, as written, allows you to change the state of the object. Immutable objects are easier to reason about, so you might want to disallow that by changing it to:
sub street { $_[0]->{street} }
It also allows you to assign any value you want to fields like state and zip. So, you may want to validate those values in the constructor, ensure that only values for the fields of the class are passed, all the values passed are defined etc.
At the end of that process, you may decide it doesn't make sense to keep writing boilerplate and use Moo or Moose to avail yourself to a richer set of features.
Even then, it helps to know what's happening under the hood.

Indirect method calling without an helper variable

Have this working short code
use 5.014;
package Module;
use warnings;
use Moose;
use Method::Signatures::Simple;
has 'commands' => (
is => 'ro',
isa => 'HashRef',
default => sub{{
open => 'my_open',
close => 'my_close',
}},
);
method run($cmd, $args) {
my $met = $self->commands->{$cmd} if exists $self->commands->{$cmd};
$self->$met($args) if $met;
#-----
#how to write the above two lines in one command?
#the next doesn't works
# $self->commands->{$cmd}($args) if exists $self->commands->{$cmd};
#-----
}
method my_open { say "module: open" }
method my_close { say "module: close" }
package main;
my $ef = Module->new();
$ef->run('open');
The main question is in the code - how to write in one line the "run" method - without the helper variable $met.
And, is here better way to do the above scenario - so calling methods based on input?
First of all, please don't do my $foo = $x if $y. You get unexpected and undefined behavior, so it is best to avoid that syntax.
The piece of code
my $met = $self->commands->{$cmd} if exists $self->commands->{$cmd};
$self->$met($args) if $met;
is equivalent to
if (my $met = $self->commands->{$cmd}) {
$self->$met($args);
}
because the exists test is superfluous here (an entry can only be true if it exists).
If we do not wish to introduce another variable, we have two options:
Trick around with $_:
$_ and $self->$_($args) for $self->commands->{$cmd};
This uses the for not as a loop, but as a topicalizer.
Trick around with scalar references:
$self->${\( $self->commands->{$cmd} )}($args) if $self->commands->{$cmd};
or
$self->${\( $self->commands->{$cmd} || "no_op" )}($args);
...
method no_op { }
Don't do something like this, because it is impossible-to-read line noise.
Neither of these is particularly elegant, and it would be better to use the cleaned-up solution I have shown above.
Just because something can be done in a single line does not mean it should be done. “This is Perl, not … oh, nevermind”.

Why am I getting this error with the perl module Catalyst::Model::Adaptor?

I'm trying to get an old project I was handed to run and I'm running into a problem with the model we have for TheSchwartz. The code looks exactly the same as a bunch of other examples to do similar things I've found online. The code is also pretty simple;
package MyApp::Model::TheSchwartz;
use Moose;
use namespace::autoclean;
extends 'Catalyst::Model::Adaptor';
__PACKAGE__->config( class => "TheSchwartz" );
sub mangle_arguments
{
my ($self, $args) = #_;
if($args->{databases})
{
if(ref($args->{databases}) eq 'HASH')
{
my %db = %{ $args->{databases} };
$args->{databases} = [ \%db ];
}
}
return %{ $args }
}
1;
The error I get is
Couldn't instantiate component "MyApp::Model::TheSchwartz", "unknown options args, catalyst_component_name, class at /usr/local/share/perl/5.14.2/Catalyst/Model/Adaptor/Base.pm line 27."Compilation failed in require at /usr/local/share/perl/5.14.2/Module/Runtime.pm line 317.
at /usr/local/share/perl/5.14.2/Catalyst/Script/Server.pm line 242.
I've tried removing the mangle_arguments function, I've tried removing the Moose usage and using "use base" instead. I always end up with the same error, and I'm really having a hard time even grokking the error message. I don't see catalyst_component_name defined anywhere in my code so it must be passed down from Catalyst::Model::Adaptor, but.. why doesnt it work?
EDIT:
here's the relevant config section:
<Model::TheSchwartz>
<args>
verbose 1
<databases>
dsn dbi:mysql:host=db.vpn;dbname=theschwartz
user user
pass password
</databases>
</args>
</Model::TheSchwartz>
This is downright silly and one of those things you try just to say you tried it without expecting it to work, but somehow this actually seems to have fixed it.
delete $args->{class};
delete $args->{catalyst_component_name};
delete $args->{args};
return %{ $args }
The model still seems to work properly, though I really expect to have broken something by just arbitrarily deleting the keys that were erroring like that.
You're trying to create a Moose class and it's not compatible with the
regular Catalyst::Model::Adaptor usage; and you are not, in your
example, using any Moose functionality anyway. This is probably,
lightly tested, what you really want.
package MyApp::Model::TheSchwartz;
use parent "Catalyst::Model::Adaptor";
__PACKAGE__->config( class => "TheSchwartz" );
sub mangle_arguments {
my ($self, $args) = #_;
if ( $args->{databases} )
{
if (ref ( $args->{databases} ) eq "HASH" )
{
my %db = %{ $args->{databases} };
$args->{databases} = [ \%db ];
}
}
return %{ $args }
}
1;
parent is sometimes considered preferable to base but they are almost interchangeable.
Update, 26 July 2012
FWIW, this is a working version of TheSchwartz being used as a model in one of my apps; it's been in production for... 2 years I think. The Cat version is a few back but not 2 years old. I'm sorry I didn't dig it up before–
package MyApp::Model::TheSchwartz;
use parent "Catalyst::Model::Adaptor";
__PACKAGE__->config( class => "TheSchwartz" );
sub mangle_arguments { %{$_[1]} }
1;
__END__
# In MyApp.pm
"Model::TheSchwartz" => {
args => {
verbose => 1,
databases => [{
dsn => "dbi:mysql:MyAppDB;host=my_host",
user => "something",
pass => "somethingsecret",
}],
}
},
The Config::General format you're using is probably to blame your ref/HASH handling. You can see from my mangle_arguments that if the args are properly formed, there is no need to do anything with them at all but flatten them to the list TheSchwartz expects.

How can I make all lazy Moose features be built?

I have a bunch of lazy features in a Moose object.
Some of the builders require some time to finish.
I would like to nvoke all the builders (the dump the "bomplete" object).
Can I make all the lazy features be built at once, or must I call each feature manually to cause it builder to run?
If you want to have "lazy" attributes with builders, but ensure that their values are constructed before new returns, the usual thing to do is to call the accessors in BUILD.
sub BUILD {
my ($self) = #_;
$self->foo;
$self->bar;
}
is enough to get the job done, but it's probably best to add a comment as well explaining this apparently useless code to someone who doesn't know the idiom.
Maybe you could use the meta class to get list of 'lazy' attributes. For example:
package Test;
use Moose;
has ['attr1', 'attr2'] => ( is => 'rw', lazy_build => 1);
has ['attr3', 'attr4'] => ( is => 'rw',);
sub BUILD {
my $self = shift;
my $meta = $self->meta;
foreach my $attribute_name ( sort $meta->get_attribute_list ) {
my $attribute = $meta->get_attribute($attribute_name);
if ( $attribute->has_builder ) {
my $code = $self->can($attribute_name);
$self->$code;
}
}
}
sub _build_attr1 { 1 }
sub _build_attr2 { 1 }
I've had this exact requirement several times in the past, and today I actually had to do it from the metaclass, which meant no BUILD tweaking allowed. Anyway I felt it would be good to share since it basically does exactly what ether mentioned:
'It would allow marking attributes "this is lazy, because it depends
on other attribute values to be built, but I want it to be poked
before construction finishes."'
However, derp derp I have no idea how to make a CPAN module so here's some codes:
https://gist.github.com/TiMBuS/5787018
Put the above into Late.pm and then you can use it like so:
package Thing;
use Moose;
use Late;
has 'foo' => (
is => 'ro',
default => sub {print "setting foo to 10\n"; 10},
);
has 'bar' => (
is => 'ro',
default => sub {print 'late bar being set to ', $_[0]->foo*2, "\n"; $_[0]->foo*2},
late => 1,
);
#If you want..
__PACKAGE__->meta->make_immutable;
1;
package main;
Thing->new();
#`bar` will be initialized to 20 right now, and always after `foo`.
#You can even set `foo` to 'lazy' or 'late' and it will still work.

Perl: How to create objects on the fly?

My goal is to be able to use $obj like this:
print $obj->hello() . $obj->{foo};
And I would like to create an object inline, maybe using something like this:
my $obj = (
foo => 1,
hello => sub { return 'world' }
);
but when I try to use $obj as an object, I get an error saying that $obj has not been blessed. Is there some base class (like stdClass in PHP) I can use to bless the hash so that I can use it as an object?
For those that know JavaScript, I am trying to do the following, but in Perl:
# JS CODE BELOW
var obj = { foo: 1, hello: function () { return 'world' } };
echo obj.hello() + obj.foo;
Perl would require a little help to do this. Because it doesn't consider code references stored in hashes as "methods". Methods are implemented as entries into a package symbol table. Perl is more class-oriented than JavaScript, which proudly proclaims that it is more object-oriented (on individual objects).
In order to do that functionality, you would have to create a class that mapped references in this way. The way to get around methods in the symbol table is the AUTOLOAD method. If a package contains an AUTOLOAD subroutine, when a call is made to a blessed object that Perl cannot find in the inheritance chain, it will call AUTOLOAD and set the package-scoped (our) variable $AUTOLOAD will contain the full name of the function.
We get the name of the method called, by getting the last node (after the last '::') of the fully-qualified sub name. We look to see if there is a coderef at that location, and if there is, we can return it.
package AutoObject;
use strict;
use warnings;
use Carp;
use Params::Util qw<_CODE>;
our $AUTOLOAD;
sub AUTOLOAD {
my $method_name = substr( $AUTOLOAD, index( $AUTOLOAD, '::' ) + 2 );
my ( $self ) = #_;
my $meth = _CODE( $self->{$method_name} );
unless ( $meth ) {
Carp::croak( "object does not support method='$method_name'!" );
}
goto &$meth;
}
1;
Then you would bless the object into that class:
package main;
my $obj
= bless { foo => 1
, hello => sub { return 'world' }
}, 'AutoObject';
print $obj->hello();
Normally, in an AUTOLOAD sub I "cement" behavior. That is, I create entries into the package symbol table to avoid AUTOLOAD the next time. But that's usually for a reasonably defined class behavior.
I also designed a QuickClass which creates a package for each object declared, but that contains a lot of symbol table wrangling that now days is probably better done with Class::MOP.
Given the suggestion by Eric Strom, you could add the following code into the AutoObject package. The import sub would be called anytime somebody use-d AutoObject (with the parameter 'object').
# Definition:
sub object ($) { return bless $_[0], __PACKAGE__; };
sub import { # gets called when Perl reads 'use AutoObject;'
shift; # my name
return unless $_[0] eq 'object'; # object is it's only export
use Symbol;
*{ Symbol::qualify_to_reference( 'object', scalar caller()) }
= \&object
;
}
And then, when you wanted to create an "object literal", you could just do:
use AutoObject qw<object>;
And the expression would be:
object { foo => 1, hello => sub { return 'world' } };
You could even do:
object { name => 'World'
, hello => sub { return "Hello, $_[0]->{name}"; }
}->hello()
;
And you have an "object literal" expression. Perhaps the module would be better called Object::Literal.
A more Perlish approach is to create a separate namespace for your object's desired methods and to bless the object to make those methods available for your object. The code to do this can still be quite succint.
my $obj = bless { foo => 1 }, "bar";
sub bar::hello { return 'world' };
As gbacon suggests, if you're willing to write $obj->{hello}->() instead of $obj->hello(), you can skip the bless operation.
my $obj = { foo => 1, hello => sub { return 'world' } };
Try Hash::AsObject from CPAN.
In whatever function you're creating the object in, you need to call bless on your object in order to enable method calling.
For example:
package MyClass;
sub new
{
my $obj = {
foo => 1
};
return bless($obj, "MyClass");
}
sub hello
{
my $self = shift;
# Do stuff, including shifting off other arguments if needed
}
package main;
my $obj = MyClass::new();
print "Foo: " . $obj->{foo} . "\n";
$obj->hello();
EDIT: If you want to be able to use subroutine references to provide dynamic functionality for your objects...
First, you can create your code reference like so (within this hash constructor example):
my $obj = {
foo => 1,
hello => sub { print "Hello\n"; },
}
You can then invoke it like this:
my $obj = MyClass::new(); # or whatever
$obj->{hello}->(#myArguments);
A little cumbersome, but it works. (You might not even need the second arrow, but I'm not sure.)
$obj would be a scalar, so whatever you assign to it has to be a scalar as well. You could either say
my %obj = ( foo => 1, hello => sub { return 'world' });
or
my $obj = { foo => 1, hello => sub { return 'world' }};
The latter, with the curly braces, creates a hash reference (which is a scalar, so it can go into $obj). To get to the stuff inside a hash reference, though, you have to use the arrow operator. Something like $obj->{foo} or &{$obj->{hello}}.
Unless you need to have lists of hashes or something like that, it's generally better to use the first method.
Either way, you won't be able to say $obj->hello(). Perl uses that syntax for its own flavor of OOP, which would have the hello function in a separate package that your reference has been blessed into. Like:
package example;
sub new {} { my $result = {}; return bless $result, 'example' }
sub hello { return 'world' }
package main;
my $obj = example->new();
As you can see, the methods you can call are already defined, and it's not trivial to add more. There are magic methods you can use to do such a thing, but really, it's not worth it. &{$obj{hello}} (or &{$obj->{hello}} for a reference) is less effort than trying to make Perl work like Javascript.
It's spelled a little bit differently in Perl:
my $obj = { foo => 1, hello => sub { return "world" } };
print $obj->{hello}() . $obj->{foo};
But the code is awkward. The warning you saw about the reference not being blessed is telling you that your objects aren't implemented in the way Perl expects. The bless operator marks an object with the package in which to begin searching for its methods.
Tell us what you want to do in terms of your problem domain, and we can offer suggestions for a more natural approach in Perl.
Methods in Perl are not properties of the object like they are in Python. Methods are plain regular functions functions in a package associated with the object. Regular functions taking an extra argument for the self reference.
You cannot have dynamically created functions as methods.
Here is a quote from perldoc perlobj:
1. An object is simply a reference that happens to know which class it
belongs to.
2. A class is simply a package that happens to provide methods to deal
with object references.
3. A method is simply a subroutine that expects an object reference
(or a package name, for class methods) as the first argument.
Oh, and bless() is how you establish the connection between the reference and the package.
I recommend using Class::Struct as explained in perltoot man page.
Instead of paraphrasing the documentation, let me quote it as it explained well how this works:
"What it does is provide you a way to "declare" a class as having objects whose fields are of a specific type. The function that does this is called, not surprisingly enough, struct(). Because structures or records are not base types in Perl, each time you want to create a class to provide a record-like data object, you yourself have to define a new() method, plus separate data-access methods for each of that record's fields. You'll quickly become bored with this process. The Class::Struct::struct() function alleviates this tedium."
Still quoting from the doc is an example way on how to implement it:
use Class::Struct qw(struct);
use Jobbie; # user-defined; see below
struct 'Fred' => {
one => '$',
many => '#',
profession => 'Jobbie', # does not call Jobbie->new()
};
$ob = Fred->new(profession => Jobbie->new());
$ob->one("hmmmm");
$ob->many(0, "here");
$ob->many(1, "you");
$ob->many(2, "go");
print "Just set: ", $ob->many(2), "\n";
$ob->profession->salary(10_000);