Re: Subroutines with differing behaviour created from AUTOLOAD
by hv (Prior) on Jun 08, 2004 at 14:10 UTC
|
The general approach seems perfectly reasonable to me. However I think you have a problem with the implementation: the methods you are injecting will be closures over the current $self, so it won't work if you have more than one object in the class.
I also think the code would be easier to read (and therefore maintain) if you create the method then goto it, rather than duplicating the functionality, something like (untested):
die "No such method $AUTOLOAD. Barfing\n"
unless exists $self->{$field};
if ($self->{$field}{editable}) {
*$AUTOLOAD = sub {
my $self = shift;
$self->{$field}{value} = shift if @_;
$self->{$field}{value};
};
} else {
*$AUTOLOAD = sub {
my $self = shift;
die "Can't modify readonly field '$field'" if @_;
$self->{$field}{value};
};
}
goto &$AUTOLOAD;
Hugo | [reply] [d/l] [select] |
|
|
Untested is right. That method will DEFINITELY work with more than one object in the class. I should know - I use it in production. You are NOT "injecting"(?) closures over the current $self. Test it before you make pronouncements like that.
However, the method will have issues for other reasons. The code implies that two difference instances might have the same field be editable or not at the instance level. If that's the case, then the first instance to access the field will set its editable-ness for all instances of the class.
There are two solutions. If each instance needs to maintain editability, then you have to go to the OP's solution, which is to have AUTOLOAD evaluate everything on a case by case basis. (This can still be gotten around, but you'd have to attach the closures to the instance and have the class methods dispatch to the instance's method. Too much work!)
If the fields are editable based on the class, then hoist the editability to the class level and deal with it there.
my $class = ref($self);
my %editable = do {
no strict 'refs';
%{"${class}::__EDITABLE__"};
};
die "No such method $AUTOLOAD. Barfing\n"
unless exists $editable{$field};
if ($editable{$field}) {
*$AUTOLOAD = sub {
my $self = shift;
$self->{$field}{value} = shift if @_;
$self->{$field}{value};
};
} else {
*$AUTOLOAD = sub {
my $self = shift;
die "Can't modify readonly field '$field'" if @_;
$self->{$field}{value};
};
}
goto &$AUTOLOAD;
------
We are the carpenters and bricklayers of the Information Age.
Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose
I shouldn't have to say this, but any code, unless otherwise stated, is untested
| [reply] [d/l] |
|
|
my $test = Foo->new();
my $test2 = Foo->new();
$test->read_write("water");
$test2->read_write("wine");
print "test: should be water, is ", $test->read_write, "\n";
print "test2: should be wine, is ", $test2->read_write, "\n";
gives me:
test: should be water, is wine
test2: should be wine, is wine
I'm confused as to what you're saying will definitely work - the OP's code has an AUTOLOAD sub which defines a lexical $self, in which he defines a couple of anonymous subs which refer to that $self. Nothing that can't be fixed by adding a my $self into the subs, as I did in my code and as you've done in yours.
Hugo | [reply] [d/l] [select] |
|
|
|
|
|
|
Can you explain what
my %editable = do {
no strict 'refs';
%{"${class}::__EDITABLE__"};
};
does please? It looks to me like it's using a filehandle to populate a hash of the attributes that should be editable. Can you give an example of how to use this?
Cheers
davis
It's not easy to juggle a pregnant wife and a troubled child, but somehow I managed to fit in eight hours of TV a day.
| [reply] [d/l] |
|
|
|
|
the methods you are injecting will be closures over the current $self, so it won't work if you have more than one object in the class
Wow, apparently not. Although I realised the subs were closures (by keeping the value $field), I never realised that this would break for more than one object instance.
Does this mean that I should remove the assignment to the symbol table and live with the performance penalty?
davis
It's not easy to juggle a pregnant wife and a troubled child, but somehow I managed to fit in eight hours of TV a day.
| [reply] |
|
|
This is a case when eval saves the day: (warning untested)
die "No such method $AUTOLOAD. Barfing\n"
unless exists $self->{$field};
if ($self->{$field}{editable}) {
*$AUTOLOAD = eval <<ENDSUB;
sub {
my \$self = shift;
\$self->{$field}{value} = shift if \@_;
\$self->{$field}{value};
}
ENDSUB
} else {
*$AUTOLOAD = eval <<ENDSUB2;
sub {
my \$self = shift;
die "Can't modify readonly field '$field'" if \@_;
\$self->{$field}{value};
}
ENDSUB2
}
goto &$AUTOLOAD;
-- I'm Not Just Another Perl Hacker
| [reply] [d/l] |
|
|
|
|
|
|
No, it means only that you should declare my $self in the subs you create, as in my example - without such a declaration, the $self in the anonymous subs refers to the closest enclosing declaration (the my $self of the AUTOLOAD routine), and that is what creates the unwanted closure.
You might prefer to further reduce the potential for confusion by using a different variable name for the two: perhaps by renaming the outer $self to $object or somesuch.
Hugo
| [reply] [d/l] [select] |
Re: Subroutines with differing behaviour created from AUTOLOAD
by davis (Vicar) on Jun 08, 2004 at 18:01 UTC
|
Here's the kind of thing I ended up using:
#!/usr/bin/perl
use warnings;
use strict;
my $test = Foo->new();
print $test->read_only, "\n";
#$test->read_only("cheese"); # Will error
print $test->read_write, "\n";
$test->read_write("cheese");
print $test->read_write, "\n";
package Foo;
use vars '$AUTOLOAD';
sub new {
bless {
read_only => {
value => "foo",
editable => 0,
},
read_write => {
value => "bar",
editable => 1,
}
}
};
sub AUTOLOAD {
no strict "refs";
my ($self, $new_value) = @_;
my $field = $AUTOLOAD;
$field =~ s/.*:://;
die "No such method $AUTOLOAD. Barfing\n"
unless exists $self->{$field};
if ($self->{$field}{editable}) {
*$AUTOLOAD = sub {
my $self = shift;
$self->{$field}{value} = shift if @_;
$self->{$field}{value};
};
} else {
*$AUTOLOAD = sub {
my $self = shift;
die "Can't modify readonly field '$field'" if
+@_;
$self->{$field}{value};
};
}
goto &$AUTOLOAD;
}
sub DESTROY { 1; }
davis
It's not easy to juggle a pregnant wife and a troubled child, but somehow I managed to fit in eight hours of TV a day.
| [reply] [d/l] |