6

I have this perl object. After the object is instantiated, I'm trying to add a new method to the object within a loader method, that can then be called later.

I've tried a whole bunch of stuff that hasn't worked. Examples include:

sub loader {
    my ($self) = @_;

    sub add_me {
        my ($self, $rec) = @_

        warn "yayyyyyy";
        return $rec;
    }

    #here are the things I've tried that dont work:
    # &{$self->{add_me}} = \&add_me;
    # \&{$self->{add_me}} = \&add_me;
    # assuming the class definition is in Holder::Class try to add it to symblol table
    # *{Holder::Class::add_me} = \&add_me;

}

EDIT:

The reason that I need to do this is I'm adding a hook in my code where the user of my software will have the ability to inject their own sub to edit a data structure as they will.

To do this, they will be able to edit a secondary file that will only contain one sub and get the data structure in question passed in, so something like:

sub inject_a_sub {
    my ($self, $rec) = @_;

    #do stuff to $rec

    return $rec;
}

then inside my original object upon its instantiation, I check to see if the above mentioned file exists, and if so read its contents and eval them. Lastly, I want to make the eval'd code which is just a sub, a method of my object. To be precise, my object is already inheriting a method called do_something and i want to make the sub read in by the eval override the do_something method being inherited so that when called the sub from the external file runs.

its a weird problem :/

and it hurts me :(

Obi wan kenobi you're my only hope!

Cheers!

3
  • 2
    you want to add extra methods to specific objects, not to all objects of the class? is there a reason you can't subclass? Commented Feb 6, 2015 at 20:07
  • Objects don't have methods, classes have methods. Commented Feb 6, 2015 at 20:45
  • 1
    @ikegami: It depends on your model. All you really care about is that an instance has all the methods that are specified by its class. Ruby has both class methods (which can be invoked only on a class, not on an instance) and instance methods, which are defined in the class but are available only through instances of that class. It also has singleton methods which are unique to a given instance, which is what this OP seems to need. Commented Feb 6, 2015 at 21:42

1 Answer 1

11

If you just want to attach functionality to a specific object, and don't need inheritance, you can store a code ref in the object and call it.

# Store the code in the object, putting it in its own
# nested hash to reduce the chance of collisions.
$obj->{__actions}{something} = sub { ... };

# Run the code
my @stuff = $obj->{__actions}{something}->(@args);

Problem is, you need to check that $obj->{__actions}{something} contains a code reference. What I would suggest is to wrap a method around this procedure.

sub add_action {
    my($self, $action, $code) = @_;

    $self->{__actions}{$action} = $code;

    return;
}

sub take_action {
    my($self, $action, $args) = @_;

    my $code = $self->{__actions}{$action};
    return if !$code or ref $code ne 'CODE';

    return $code->(@$args);
}

$obj->add_action( "something", sub { ... } );
$obj->take_action( "something", \@args );

If you already know the class name you want to inject a method into, write the subroutine as normal but use the fully qualified name.

sub Some::Class::new_method {
    my $self = shift;

    ...
}

Note that any globals inside that subroutine will be in the surrounding package, not in Some::Class. If you want persistent variables use state inside the subroutine or my outside the subroutine.


If you don't know the name at compile time, you'll have to inject the subroutine into the symbol table, so you were close with that last one.

sub inject_method {
    my($object, $method_name, $code_ref) = @_;

    # Get the class of the object
    my $class = ref $object;

    {
        # We need to use symbolic references.
        no strict 'refs';

        # Shove the code reference into the class' symbol table.
        *{$class.'::'.$method_name} = $code_ref;
    }

    return;
}

inject_method($obj, "new_method", sub { ... });

Methods in Perl are associated with a class, not an object. In order to assign a method to a single object, you have to put that object into its own class. Similar to the above, but you have to create a subclass for every instance.

my $instance_class = "_SPECIAL_INSTANCE_CLASS_";
my $instance_class_increment = "AAAAAAAAAAAAAAAAAA";
sub inject_method_into_instance {
    my($object, $method_name, $code_ref) = @_;

    # Get the class of the object
    my $old_class = ref $object;

    # Get the special instance class and increment it.
    # Yes, incrementing works on strings.
    my $new_class = $instance_class . '::' . $instance_class_increment++;

    {
        # We need to use symbolic references.
        no strict 'refs';

        # Create its own subclass
        @{$new_class.'::ISA'} = ($old_class);

        # Shove the code reference into the class' symbol table.
        *{$new_class.'::'.$method_name} = $code_ref;

        # Rebless the object to its own subclass
        bless $object, $new_class;
    }

    return;
}

I left out the code to check whether or not the instance has already had this treatment by checking if its class matches /^${instance_class}::/. I leave that as an exercise for you. Creating a new class for every object is not cheap and will cost memory.


There are valid reasons to do this, but they are exceptional. You should really, really question whether you should be doing this sort of monkey patching. In general, action at a distance should be avoided.

Can you accomplish the same thing using a subclass, delegation or role?

There already exist Perl OO systems which will do this for you and much much more. You should be using one. Moose, Moo (via Role::Tiny) and Mouse can all add roles to an instance.

Sign up to request clarification or add additional context in comments.

16 Comments

@Rooster As I pointed out in the answer, that's a symbolic reference. strict does not allow them (with good reason) and you have to temporary turn that restriction off. "Something a guy told me to do" isn't a great rationale, I don't care how many years they've been at it, if you don't understand the rationale you probably shouldn't be messing with it. This is one of those "solutions" that's likely to cause more problems. How about ask a question about the original problem? If you link it here in the comments I'll have a look.
@ikegami An AUTOLOAD will slow down every method call, they're very bug prone, they're hard to make seamless (for example, making can() work) and you have to inject that AUTOLOAD into the calling class and deal with the possibility that you're masking an existing AUTOLOAD (or will get masked in a "clever" subclass). I avoid them whenever possible and tear them out when I find them. Make that your own answer and we can discuss it further.
@Rooster Re. "I kind of have to take it on faith that this is one of those exceptional cases as a 15 year perl guy told me to do it this way" The nice thing about Stack Overflow is that there are lots of "15 year perl guys" (and likely even 16, 17, 18+ year perl guys), who don't hesitate to point out each others' mistakes. You don't have to listen to just one guy when you have a whole group of people available to help.
@Rooster No, so long as you wind up with a code reference. eval'ing code is a huge red flag. You really should post your original problem, your solution is raising pretty much every Unnecessary Code Generation red flag.
@Borodin, It will slow down every call to the "loaded" methods. I think that's what he meant.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.