In a post a bit ago, I had this example:
sub AUTOLOAD { # that object's data. our $AUTOLOAD; my $method = $AUTOLOAD; $method =~ s/.*:://; return if $method eq 'DESTROY'; our $self = shift; if(! exists $self->{$method}) { my $super = "SUPER::$method"; return $self->$super(@_); } $self->{$method}->(@_); }This takes method calls and re-dispatches them to the methods actually stored inside of the object. The constructor would look something like:
sub new { my $package = shift; our $self = bless \my %self, $package; my $foo; # instance var $self{get_foo} = sub { $foo }; $self{set_foo} = sub { $foo = shift; } return $self; }
%self
and $self
don't hold any instance data; they just hold coderefs that are closures that close over the instance data. This takes a lot more RAM than ordinary Perl classes. But that doesn't work with $ob->can()
.
sub new { my $type = shift; my $package = $type . sprintf '::X%09d', our $counter++; push @{$package.'::ISA'}, $type; my $self = bless \%{$package.'::'}, $package; sub method ($&); *method = sub ($&) { my $name = shift; *{"$package\::$name"} = shift; }; my $foo; # instance variable method get_foo => sub { $foo; }; method set_foo => sub { my $self = shift; $foo = shift; } return $self; }This is the basis of my Object::Lexical module but here I've inlined the code. O::L also does some bizarre evil stuff with local'izing variables in a loop constructed with goto so that the localizations don't go out of scope at the end of the loop... anyway. That code puts each object instance into its very own package that subclasses the package that it's supposed to be in. Each method is a coderef (probably a closure) stuck right into the symbol table there. Then
$ob->can()
works, and so does foo()
for calling another method as a function. And a lot of other things work too.
I wanted can()
because I'm doing $ob->can($method) ? $ob->can($method)->($ob, @args) : error
for some late binding garbage and that's a lot neater than looking to see if some AUTOLOAD
method raised an error. And it doesn't have the CPU overhead of AUTOLOAD
.
What I don’t see is why you picked closure-based objects?
As for the package-per-instance approach, that works, but the packages never get garbage-collected unless you do that manually, which is tricky to get right in the best of cases, and I think deleting packages has some gotchas anyway.
As for can
, you know that it’s just a method, right? Assuming you use the first approach that relays through a dispatch table rather than the one where you create packages:
sub can {
my $self = shift;
my ( $method ) = @_;
return exists $self->{ $method }
? $self->{ $method }
: $self->SUPER::can( $method );
}
(Employ SUPER or Class::C3 as appropriate.)
That’s much neater than screwing around in the symbol tables.
Re:On closure-based objects
scrottie on 2007-08-03T08:11:34
As for why closure based objects, so that I didn't have to go through and change all of the thousands of variables to be $self{var} or $self->{var} or the like instead of $var.
As for AUTOLOAD, that's a good point (redefining can). I'm not sure what I was thinking in just not doing that. But it's about the same amount of code either (mucking with the symbol table to redispatching with AUTOLOAD), and I like the idea of not having to go through AUTOLOAD for each hit.
I'm probably going to be moving a lot of variables from being instance data to local to the method and it'll be nice not to have to go back and change them back to ordinary scalars but instead just change the scope.
-scottRe:On closure-based objects
Aristotle on 2007-08-03T14:27:07
Does the expense of going through AUTOLOAD really matter? Because it’s most definitely not the same amount of code, qualitatively speaking. The package-based code is a lot more conceptually complex, and has much more tangible problems than the overloaded
can
method (defies garbage collection vs. slows method calls down a tad).As for just changing the scope: what do you mean? I don’t quite understand that statement.
It’s really easy to go into code that has cut-and-paste-itus and try to clean it up and just utterly fail because you start fixing one thing, get distracted and start fixing another thing, and so on and so forth.
Yeah. Unraveling a ball of mud is an art. A painful, slow and proportionally unrewarding art, but an art nonetheless.
I feel like I’ve successfully decoupled logic and data to a large degree. Does the end justify the means?
;)
As I opined before, yes it does, as long as the end is a straightforward codebase with no tricks. It’s fine to get there using evil tricks to keep the code working between steps, as long as you wipe away the magick once it has served its purpose.