Re: Altering the inheritance path of an object
by tobyink (Canon) on Sep 27, 2012 at 15:22 UTC
|
Yes. The basic technique is to create a new package which inherits from the old one but adds some new methods, then rebless the object into that new package. Here's a quick example:
use 5.010;
use Math::BigInt;
# quite big!!
my $seven = Math::BigInt->new('7');
# $seven cannot "speak", so this warns
eval { $seven->speak } or warn $@;
{
package Math::BigInt::Speaker;
our @ISA = qw(Math::BigInt);
use Scalar::Util qw(blessed);
use Carp qw(confess);
sub upgrade {
my ($class, $instance) = @_;
confess "can only upgrade Math::BigInt objects"
unless blessed $instance && $instance->isa('Math::BigInt')
+;
bless $instance => $class;
}
sub speak {
CORE::say($_[0]);
}
}
# Swaps $seven into our new class.
Math::BigInt::Speaker->upgrade($seven);
# $seven can now "speak"
eval { $seven->speak } or warn $@;
If you use Moose you can do this in a much nicer and more organised way. (Though it's basically the same thing happening under the hood.) You'd just create a new anonymous role, and apply that role to the object.
use 5.010;
use Math::BigInt;
use Moose ();
# quite big!!
my $seven = Math::BigInt->new('7');
# $seven cannot "speak", so this warns
eval { $seven->speak } or warn $@;
my $speaker = Moose::Meta::Role->create_anon_role(
methods => {
speak => sub { CORE::say($_[0]) },
},
);
Moose::Util::apply_all_roles($seven, $speaker);
# $seven can now "speak"
eval { $seven->speak } or warn $@;
perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'
| [reply] [d/l] [select] |
|
Hello.
I read Moose::Manual::Roles.pod and saw the car which applied Breakable roll on the fly, like you demonstrated. I heard several times Ruby people talks something like this... I mean instance extended on the fly.
I , who have little experience for OO programing, thinks why people do like these, I would do ... say
#!/usr/bin/perl
{
package TalkativeInt;
use 5.010;
use strict;use warnings;
use base qw/Math::BigInt/;
sub talk {
say "I say ". $_[0]->value;
}
sub value {
$_[0]->bstr();
}
1;
}
use 5.010;
use strict;use warnings;
my $p=TalkativeInt->new(7);
$p->talk;
with old school perl and extend with Moose,
#!/usr/bin/perl
{
package TalkativeInt;
use 5.010;
use Moose;
extends 'Math::BigInt';
sub talk {
say "I say ". $_[0]->value;
}
sub value {
$_[0]->bstr();
}
1;
}
use 5.010;
use strict;use warnings;
my $p=TalkativeInt->new(7);
$p->talk;
Do you have opportunity to use apply_all_roles in real world? This is just my curiosity.
I really enjoy your Moose, ternary tree, lazy worker, I learned a lot.
regards
| [reply] [d/l] [select] |
|
A project I'm working on now is a system for data transformation pipelines. Kind of like a makefile for data. So your pipeline might be something like:
- Output the following tabular data as Excel:
- Run the following query on the following database, resulting in tabular data.
- SELECT person.name, ... FROM ...
- Create a temporary database with these tables in it:
- Create a table named "person" and load it with tabular data:
- Parse this data as CSV:
- Download data from http://example.com/people.csv
- Create a table named "companies" and load it with tabular data:
- Parse this data as tab-delimited data:
- Download data from http://example.com/client-companies.tab
So you'd load up the pipeline, and tell the pipeline that you want the Excel file. And it thinks: OK, so I need to query this database, but first I need to build the database, how do I do that... I need to create it and load these tables... how do I do that... etc. You get the picture.
And in fact, some details of the pipeline are inferred during processing. We often don't want to hard-code file formats like CSV into the pipeline, but instead infer them from HTTP content-type headers and so forth.
It's still at a fairly early stage, but it's progressing quite nicely, and Moose roles are an important part of the implementation.
The reason being that at many stages in the process you need mix-and-match transformations. You need an object which accepts CSV and outputs Excel, or accepts tab-delimited data and outputs Excel, or accepts CSV and outputs an SQL table, or accepts an SQL table and outputs an HTML table.
OK, so each of these could be a class, but it's far easier to do as a role. When you come to a stage in processing, you instantiate a very generic object (which just has a few debugging methods, a unique identifier string, a link back to the pipeline object, etc) and compose it with the roles it needs to be able to handle (read CSV, write Excel).
The technique which you showed in your example is to make a subclass of Math::BigInt, and stuff the new methods into that. It's worth noting that actually, this is exactly how Moose role composition works internally - it creates a new class for you, and reblesses the object into that class. But it's all done on the fly at run-time, so you don't need to create modules for each class, choose names for them, etc. All that is abstracted away.
perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'
| [reply] [d/l] |
|
Re: Altering the inheritance path of an object
by zwon (Abbot) on Sep 27, 2012 at 15:16 UTC
|
You can create a new class with required inheritance path and rebless $obj to this class. Otherwise it is not possible, as inheritance defined by the value of @ISA package variable, and so it is property of the class and not an object. | [reply] [d/l] [select] |
Re: Altering the inheritance path of an object
by Fletch (Bishop) on Sep 27, 2012 at 15:20 UTC
|
If you're wanting to alter the behavior of individual instances then you may be interested in strategy pattern. You'd have different classes implementing the different behaviors and then your original objects would have a reference to (possibly singleton) instances of the corresponding strategy implementors as appropriate.
The cake is a lie.
The cake is a lie.
The cake is a lie.
| [reply] |
Re: Altering the inheritance path of an object
by Athanasius (Archbishop) on Sep 27, 2012 at 15:14 UTC
|
1:07 >perl -MData::Dumper -E "say join(q[, ], @Data::Dumper::ISA); @D
+ata::Dumper::ISA = ('fred', 'wilma'); say join(q[, ], @Data::Dumper::
+ISA);"
Exporter
fred, wilma
But why would you want to do this?
Update: Sorry, I saw “class’, missed “object”. I apologise for the noise. But I’d still like to know, why do you want to do this?
Athanasius <°(((>< contra mundum
| [reply] [d/l] |
|
I work with a complex inheritance layout and have the impression that some classes can gain some functionality by inserting a new SUPER class (without breaking the code). But I prefer to work at the "test level" instead of changing the inheritance path in the classes' files. This way, if I am proved wrong, I don't have to change anything (and there is no risk to leave the classes with a wrong inheritance path) .
| [reply] |
|
It sounds like you are pursuing a rather intriguing testing scenario with regard to a complex legacy application. I also have experienced that complex and sometimes interlocking inheritance-paths can create situations that are extremely difficult to test with any confidence that the testing scenario that you’re trying to set up actually matches the reality of the application. Never really found a good way to approach it, and I didn’t think of this. I’d love to read a Meditation about what you are doing and/or about the issues that you’re dealing with now, someday soon ...
| [reply] |
|
I would suggest creating an empty subclass then:
package Test::It;
our @ISA = qw(Package::Under::Test Utility::Subs);
1;
# ... later, in another file
my $under_test = Test::It->new(...);
This way any method is first searched in the Package::Under::Test's hierarchy and only then in Utility::Subs. | [reply] [d/l] |
|