karavelov has asked for the wisdom of the Perl Monks concerning the following question:

Hello wise monks,

I am trying to figure how to replace object methods at runtime. Does it exist symbol table per object(instance) or only per class/package? My simple test case is this:
#!/usr/bin/perl { package foo; sub new { my $type = shift; return bless {}, $type; } sub baz { print "foo-baz\n"; } sub m { my $self = shift; # do some init here print "replace\n"; *m = sub { $self->baz }; $self->m(); } } { package bar; use base foo; sub baz { print "bar-baz\n"; } } # main my $x = new foo; my $y = new bar; $x->m; $y->m;
When executed it produces:
replace foo-baz foo-baz
It is clear that the line:
*m = sub { $self->baz }
replaces the sumbol entry in the class/package but not in the working instance/object.
If I have written
*m = sub { my $self = shift; $self->baz }
then the correct method baz will be called but the method "m" will be replaced only once.
Is there any hope to do this with symtable manipulation?
The current solution is to make a dispatch table per instance and manipulate its entries. I do not like it. The method invocation syntax now is :
$self->{m}->($self,@args);
But if I could skip this level of indirection I will be happy.

Thanks in advance

Replies are listed 'Best First'.
Re: replace object methods at runtime
by pc88mxer (Vicar) on Jun 24, 2008 at 13:18 UTC
    Does it exist symbol table per object(instance) or only per class/package?
    A symbol table only exists per class/package.

    You might be interested in Object::Prototype which implements a prototype object model like Javascript has. That will allow you to implement methods on a per object basis.

      Thanks, sounds interesting this prototype objects. Quick view of the source reveals that in this manner can be defined new methods but I doubt that could be replaced already existing ones. The solution is to make some kind of dispatch table indexed by id of the object and method and than create subs in AUTOLOAD. But I really doubt that the new method could shadow the already existing method.
      For now I think to just clean the double dispatch solution that I already have.
        If you don't need something as heavy-duty as Class::Prototyped, you can use Class::Unique, which gives every instance a unique subclass whose symbol table you can tweak at will.
Re: replace object methods at runtime
by salva (Canon) on Jun 24, 2008 at 13:30 UTC
    But if I could skip this level of indirection I will be happy.

    Well, you can hide it using AUTOLOAD

    # untested sub AUTOLOAD { if (my ($pkg, $name) = $AUTOLOAD =~ /^((?:.*::)?)(.*)$/) { $name =~ /^_default_(.*)/ and croak "method $1 not found"; my $sub = sub { my $method = $_[0]->{m}{$name}; goto &$method if defined $method; my $default = "_default_$name" shift->$default(@_); } no strict 'refs'; *$AUTOLOAD = $sub; goto &$sub; } croak "bad method name $AUTOLOAD"; }
    and then declare the default methods for your objects as:
    sub _default_foo { ... } sub _default_bar { ... } ...
      Thank you for all the suggestions and especially to salva - I have taken the AUTOLOAD road (there is a rhyme here :)

      Thanks also to Narveson. Basically the AUTOLOAD makes you the wrapper functions when they are needed. It is really good because I don't want to hand write them by hand.

      I know that one day I have to go for Moose. May be sooner than I expected.

      Thanks again
Re: replace object methods at runtime
by Narveson (Chaplain) on Jun 24, 2008 at 14:12 UTC

    In place of

    $self->{m}->($self,@args);

    would you like $self->m(@args)?

    All you have to do is cover up the syntax you dislike with a wrapper, like so:

    package foo; # dispatch table my %m_implementation_for; # wrapper sub m { my ($self, @args) = @_; my $implementation = $m_implementation_for{$self}; $implementation->($self, @args); }

    Afterthoughts: or more tersely,

    sub m { my ($self, @args) = @_; $m_implementation_for{$self}($self, @args); }

    To be still terser, replace the implementations with closures:

    my %m_closure_for; # after the implementations have been defined foreach my $object (keys %m_implementation_for) { $m_closure_for{$object} = sub { $m_implementation_for{$object}($object, @_); }; } # terser wrapper sub m { my ($self, @args) = @_; $m_closure_for{$self}(@args); }
Re: replace object methods at runtime
by dragonchild (Archbishop) on Jun 24, 2008 at 15:18 UTC
    You're looking for Class::MOP and, generally, Moose.

    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: replace object methods at runtime
by kyle (Abbot) on Jun 24, 2008 at 16:38 UTC
Re: replace object methods at runtime
by DrHyde (Prior) on Jun 25, 2008 at 10:17 UTC
    You can't replace methods per-object, only per class or package. So instead, how about replacing the object's class! Let's assume that you want to replace the foo method in $object ...
    use Scalar::Util qw(refaddr); my $newclass = q[ package $CLASS::FakeClass$OBJID; use base qw($CLASS); sub foo { my $self = shift; ... blah blah blah ... } ]; $newclass =~ s/\$CLASS/ref($object)/eg; $newclass =~ s/\$OBJID/refaddr($object}/eg; eval $newclass; bless $object, ref($object)."::FakeClass".refaddr($object);
    That creates a new class just for that object (the one-to-one class-to-object correspondence is guaranteed by using the object's address in memory as part of the class name), which inherits from the class that the object is already an instance of, then reblesses it. Any methods defined in the new class will override those in the original class, and any others will just be passed through to the original class in the usual way.

    The one problem with this is that you'll need to be really careful if you serialise and later unserialise objects. In particular, objects are extremely unlikely to be unserialised at the same address in memory, which may lead to later namespace clashes.

    Code is untested, but I do something similar quite often, so the principle is correct even if I typed it wrong :-)

Re: replace object methods at runtime
by BrowserUk (Patriarch) on Jun 24, 2008 at 15:57 UTC
      Sure ,

      A little context first. I am writing SNMP Trap handling framework. I have started with Net-SNMP's snmptrapd with embedded perl interpreter and I have written the first version of the framework and some heavy duty trap handlers for it. Then I realized that it leaks like hell - 1G RAM for 2-3 hours. And it is not my fault because it leaks with empty handlers a lot of memory too

      The next step was to write my own trap-daemon based on SNMP_Session.pm and BER.pm. It take me one morning the daemon and the afternoon to port and tune the existing trap-handlers.

      So now the architecture is quite configurable and pluggable but is modeled upon the initial implementation. It consists of one central process that at the start loads the configured modules and then fires the appropriate handlers for the received traps.

      All the trap handlers inherit from one class "Trap" that makes simple decoding of the trap and manages the live-cycle of the handler. I have decided to spawn new process for each handler and to communicate via pipes with it. In this way I could get lazy forking and initialization of the handlers and to manage situations like the death of some workers. So for now I have code that goes like this:

      sub new { my $type = shift; my $class = ref $type || $type; my $self = { trap => [], cfg => shift }; bless $self, $class; return sub { $self->receiver(@_) }; } sub _default_receiver { my ($self,$vars) = @_; my $fd; if ( my $pid = open($fd, "|-" )){ # Fork $fd->autoflush(); $__PACKAGE__::to_die{$pid}=$self; $self->{receiver} = sub { # Replace receiver in parent my ($self1,$v)= @_; store_fd($v,$fd); # Send }; $self->receiver($vars); # The first trap }else{ $self->init(); # Init child here while ( my $v = fd_retrieve( \*STDIN ) ) { my $r = {}; ... do some work here .... push @{$self->{trap}},$r; $self->handler(); } $self->destroy(); } }

      The constructor return a closure that will be run for each received trap. The idea of the "dynamic method rewriting" is that the "receiver"(_default_receiver) will fork new process only the first time and then replace itself with "receiver" (the closure) that just feeds the created worker with data. If this process dies we deleting the "receiver", so the next time it will call the _default_receiver and will fork new process. (SIGCHLD handler not shown in the code).

      This version uses the AUTOLOAD approach suggested by salva. I am not coping the code of AUOTLOAD sub that looks quite like the code he suggested

      I am open to all kinds of suggestions and advises.

      Thanks in advance

        Sure ,

        Thank you for indulging me. I've seen this kind of facility requested (or described in other languages), but I've never really seen a non-hypothetical use-case for it. And I'm always more interested in real use cases than hypothetical ones.

        That said, without fully understanding the architecture you are describing, it sounds like a complex way of coding a simple boolean condition? Essentially:

        sub method { my( $self, @args ) = @_; ... if( $self->_pipeExistsAndIsGood ) { ## communicate with pipe } else { ## $self->_forkAndStorePipe(); } }

        I can see that 'dynamic method rewriting' is a way to avoid a boolean test, which might provide a small efficiency gain, but trading that for the complexity and 'action at a distance' involved runtime determination of the code behind a method, along with parent & child time forked instances, just seems like a recipe for complex and difficult to trace bugs and failures.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
        Maybe, you can use Class::StateMachine. It lets you define several implementations for the same method and then select which one to use based on an internal "state" property.

        Internally, it uses a reblessing approach very similar to the one exposed by DrHyde below.