Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid
 
PerlMonks  

on the fly methods

by Ojosh!ro (Beadle)
on Feb 27, 2007 at 16:26 UTC ( [id://602324]=perlquestion: print w/replies, xml ) Need Help??

Ojosh!ro has asked for the wisdom of the Perl Monks concerning the following question:

Brothers,

Is there a way to do the following directly?

$this->{_codestring} = 'sub{ return "Don\'t think so"; }'; $this->makeMethod(); #1) my $floop = $this->callMethod(); #2)

#1) Evals the codestring and sets the coderef in $this->{_coderef}
#2) Calls the coderef and returns its result

So, can de coderef directly be adressed as if it's a method of the $this object ?

my($s,$b,@c,@b)=qw(0 0 5C 5F 2F 20);while(int($s+.45)<2){$b[0].=chr(hex($c[int($s+.45)]));$b[1].=chr(hex($c[int($s+2.45)]));$s+=.55}while(1){print $b[$b];$b--;$b*=$b}#Fishnets

Replies are listed 'Best First'.
Re: on the fly methods
by Fletch (Bishop) on Feb 27, 2007 at 16:35 UTC

    This is Perl, not Ruby. Methods are subs in a given package, not properties of individual instances. You possibly could do some dark magic with AUTOLOAD (make it lookup say $self->{_instancemethods}->{$AUTOLOAD} and call that if it exists), or eval new code into the instance's package (eval qq{ package @{[ ref $self ]}; sub { "NEW CODE" }}; however that's going to make the method available on all instances of that class (and its descendants) not just the one in $self)).

    Perhaps you could elaborate on what you're trying to do and you might get better suggestions (e.g. yes, you're trying to emulate Ruby's

    class << obj def new_method "fweeee" end end
    in order to add a method on a single instance).

    Update: Formatting and wording tweaks.

      If you use any of Class::Trait, Class::MOP, or Moose then objects can have their own methods. (I'm guessing that Moose does this because it uses MOP which provides this service).

      ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

        Actually Class::MOP does not provide for a (direct) way to add methods to an instance, it mostly deals with just classes. And with Moose, there is no way to add a single method on a per-instance basis really either, you do it with runtime Roles instead, like this:

        { package My::Class; use Moose; sub hello { "Hello" } package My::Role; use Moose::Role; sub foo { "Foo" } } my $obj_1 = My::Class->new; my $obj_2 = My::Class->new; My::Role->meta->apply($obj_1); print $obj_1->foo; # print "Foo" print $obj_2->foo; # dies with a method not found error
        Also Class::Trait does this same type of thing using Traits instead of Roles.

        Ruby accomplishes this type of thing with "Eigenclasses" which were in the Perl 6 metamodel at one time, but were not backported to Moose (because they tended to make simple metaclass things overly complex). The Ruby idiom of:

        class << obj def new_method "fweeee" end end
        roughly translates into this:
        obj.add_singleton_method(lambda { "fweee" })
        where add_singleton_method is just a method in Object. Something like this could be added to Moose without too much trouble at all really (no AUTOLOAD tricks needed either).

        -stvn
        I had never heard of Moose before this, but I think it's potentially something I could one day use in all my new OO code. Wow, just wow.

        -Paul

Re: on the fly methods
by Thelonius (Priest) on Feb 27, 2007 at 16:54 UTC
    sub makeMethod { my $this = shift; $this->{_coderef} = eval $this->{_codestring} } sub callMethod { my $this = shift; $this->{_coderef}->(@_) }

    If you want to do something like this:

    $this->makeMethod( "somenewname", "some code" ); $this->somenewname();
    then I think you'll have to use AUTOLOAD or some module that provides that functionality.
Re: on the fly methods
by agianni (Hermit) on Feb 27, 2007 at 18:13 UTC
    Class::Prototyped is probably a good option for this. It allows you to:
    use Class::Prototyped; my $p = Class::Prototyped->new(); $p->reflect->addSlot( sub1 => sub { print "this is sub1" } ); $p->sub1 # prints "this is sub1";
    It also provides a whole lot of other dynamic inheritance options if you want to build entire class hierarchies on the fly.
Re: on the fly methods
by Rhandom (Curate) on Feb 27, 2007 at 16:47 UTC
    There are various ways. The least dirty is to use the AUTOLOAD system as in the following.
    package Foo; use strict; use vars qw($AUTOLOAD); use Carp qw(croak); sub new { bless {}, __PACKAGE__ } sub add_method { my ($self, $name, $code) = @_; ### This following chunk could use Scalar::Util::blessed ### if they have perl 5.8 installed. We will operate without it if (! ref $code) { # scary and sort of ugly but thats what the OP wanted $code = eval $code; } # (! Scalar::Util::reftype($code) ne 'CODE') { if (! UNIVERSAL::isa($code, 'CODE')) { croak "Usage : add_method(method => sub {})\n add_method(method => 'sub {}')"; } $self->{'_methods'}->{$name} = $code; } sub AUTOLOAD { my $self = shift; my $name = $AUTOLOAD =~ /(\w+)$/ ? $1 : croak "Invalid method \"$A +UTOLOAD\""; my $code = $self->{'_methods'}->{$name} || croak "No such method \"$name\" via package ".__PACKAGE__; $self->$code(@_); } my $foo = Foo->new; $foo->add_method(bar => sub { print "bar\n" }); $foo->add_method(baz => 'sub { print "baz\n" }'); $foo->bar; $foo->baz;

    The other way of doing this sort of thing involves manipulating the symbol table which isn't hard, but I wouldn't recommend if you are just starting out.

    my @a=qw(random brilliant braindead); print $a[rand(@a)];

      You just broke overloading:

      # (! Scalar::Util::reftype($code) ne 'CODE') { if (! UNIVERSAL::isa($code, 'CODE')) {

      How about:

      unless (defined &$code) {

      Also, isa() is a method, not a function. Using it as a function can break many things.

        Sorry to the original thread as we are now a bit off the beaten topic.

        Yes. It is a method. It will skip $code's ->isa. It breaks overloading. It also works in 99% of the use cases (probably more) that the general programming population will use and in those cases where somebody has created an object that pretends to be code they can simply pass sub { &$code } and suddenly it works fine. Changing to $code->isa('CODE') in this case would actually break 99% of use cases because it only allows for objects that are CODE (not bare coderefs). I think it is wonderful that Perl 5 (and other languages) can call methods in a more functional form if the need arises.

        Using defined &$code almost works but it breaks if a non-coderef reference is passed in. It also doesn't go the full route of supporting overloaded objects so it isn't any more of a solution.
        The only thing that "might" be able to handle 100% of cases is something like
        use Scalar::Util qw(blessed); my $is_code = blessed($code) ? $code->can('&{}') : ref($code) eq 'CODE +';

        UPDATE: - Yup - I missed blessed coderefs which would have to be checked for with eval { defined &$code }. The corrected code might look like
        my $is_code = ! blessed($code) ? ref($code) eq 'CODE' : $code->can('&{}') || $code->isa('CODE') # arguably not a good test (bless +{}, 'CODE') || eval { defined &$code }; # arguably bad because it ge +ts called in some contexts


        Arguably that isn't all that bad. I'm sure there is probably still a case I'm missing. I do know of a few companies that are still pre-Perl 5.8 and the Scalar::Util::blessed route isn't even an option for them.

        Yes. I agree that things can break. Perl 5 has problems in being able to type data or easily (s/easily/succinctly/) detect the type or capabilities of data. There are many things that can break during argument passing in Perl 5. There are basic things that can be done to check for basic types of data. If advanced users want to pass in advanced types, the basic checks won't prohibit them - they just force them to wrap them in more palatable forms. Although many times the "basic" checks I've seen are indeed poorly done and restrict viable options that would otherwise work just fine.

        I've probably already gone too far. There are many who feel very strongly about this issue. But the picture painted is usually more severe that is necessary and when presented as a blanket statement hides useful, working use cases.

        Often on Perlmonks there is the assumption that one doesn't know what they are doing when they actually do. And of course there are those that assume they know what they are doing when they actually do not.

        my @a=qw(random brilliant braindead); print $a[rand(@a)];
Re: on the fly methods
by Zaxo (Archbishop) on Feb 27, 2007 at 19:38 UTC

    The class's object method can call an instance's _codemethod:

    package Foo; sub makemethod { my $obj = shift; $obj->{'_codemethod'} = shift; # takes a real code reference # in this implementation, # not a string, so renamed } sub callmethod { my $obj = shift; $obj->{'_codemethod'}(@_); } # . . .
    Don't need no stinkin' Ruby ;-)

    After Compline,
    Zaxo

Re: on the fly methods
by fenLisesi (Priest) on Feb 27, 2007 at 16:39 UTC
    You could do:
    $this->{_coderef} = sub{ return "Don\'t think so"; }; ## ... and later: printf "%s\n", $this->{_coderef}->();
    Do you have to create your methods on the fly? There could be better ways. Cheers.

    Update: Changed "_codestring" to "_coderef"

Re: on the fly methods
by Moron (Curate) on Feb 27, 2007 at 16:53 UTC
    No need to eval the code, for example:
    $this -> { _codereference } = sub { return "Do " . shift() . " so.\n"; + } # ... print FH $this -> { _codereference }{ "believe" );

    -M

    Free your mind

Re: on the fly methods
by stvn (Monsignor) on Feb 27, 2007 at 21:54 UTC
Re: on the fly methods [Thanks!]
by Ojosh!ro (Beadle) on Feb 28, 2007 at 08:55 UTC
    Thank you brothers.

    Firstly, my excuses, I have been unclear. I think however I got enough replies and suggestions to try.
    Al2O3 I'm unfamiliar with. Anything I want to try?
    But thanks very much. I'll go through the options you offered and see what flavour suits me best.

    my($s,$b,@c,@b)=qw(0 0 5C 5F 2F 20);while(int($s+.45)<2){$b[0].=chr(hex($c[int($s+.45)]));$b[1].=chr(hex($c[int($s+2.45)]));$s+=.55}while(1){print $b[$b];$b--;$b*=$b}#Fishnets

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://602324]
Approved by kyle
Front-paged by Old_Gray_Bear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others goofing around in the Monastery: (3)
As of 2024-04-24 02:49 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found