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

Old and Wise Monks,

I have come up with an interesting solution (I hope) to a tricky problem. I've almost got it but just need some advice.

I have a class, let's call it BigDB. I would like to dynamically generate object method names for a BigDB instance based upon some stuff read in from a file. Within the BigDB constructor, I say:

my $self = {}; # hash ref ... foreach my $method (@method_names_from_file) { $self->{$method} = makeCommandSub($stuff); }
And within makeCommandSub(), I am creating a code ref and returning it:
my $code = sub { my $self = shift; print "Dang, it worked.\n"; }; return $code;
So, I go ahead and create my new BigDB() object and everything seems to go fine. My debugger shows me that my new object $myDB now has CODE type fields:
$myDB <hash> ->{method1} <CODE> ->{method2} <CODE>
Alas, when I try to run these CODE blocks as I would a method, it doesn't work.
$myDB->method1();
yields...
Can't locate object method "method1" via package "BigDB" at line...
I have also tried &{$myDB->method1}. I AM able to run normal subroutines within the BigDB class.

So what's the proper way (if any) to execute these CODE refs in my object/hash ?

Much thanks

-zaven.pl

Replies are listed 'Best First'.
Re: Dynamically Creating (and Calling) Object Methods
by gaal (Parson) on Jan 07, 2007 at 08:33 UTC
    Almost there!

    $myDB->{method1}->();

    Note that you are adding these methods per *instance*, not to the whole class -- this is a technique used in prototype OOP. If you wanted to dynamically augment your class, the hash you need to edit is actually the BigDB package's symbol table.

      Thanks for the reply. That does make sense. After some googling I tried this approach in BigDB::new()...
      # $cmd = "method1" eval { no strict 'refs'; *{'BigDB\::$cmd'} = makeCommandSub($CMDS->{$cmd}); } or die "Can't modify the symbol table: $!";
      ...but this does not seem to result in a usable $myDB->method1() as I would like. ("Cannot locate method "method1" via package BigDB....")

      So next I tried hard-coding the new symbol name like so:

      *BigDB::method1 = makeCommandSub(...);
      and that works! So I just need to know how to use a string as part of the symbol reference. More clues? :-)
        First, unrelated to your main task: you don't need to say eval {...} or die $!, for several reasons. The first is that if it throws an exception, you can probably use the original exception :) Second, the or there is dangerous unless makeCommandsSub is guaranteed to return a true value; the valuation of an eval is (like any other block of Perl 5) its last evaluated thing. So
        eval { 0 } or die "this dies, but not because of an exception in the i +nner block!";

        Finally, $! is wrong in this case, you need $@ for the exception.

        But to your real question:

        You said 'BigDB\::$cmd'. But because of the single quotes, this creates literally a '$cmd' package in BigDB's symbol table! (Check by printing keys %BigDB::.) The correct code is

        *{"BigDB::$cmd"} = ...

        You probably got the backslash from code that used a variable containing the caller, where it's needed to make Perl not think you're talking about a fully qualified variable:

        *{"$caller\::methodname"} = ...
        'BigDB\::$cmd' is single-quoted, so creates a method named $cmd in package BigDB\ instead of a method named method1 in package BigDB. Try double quotes (where the backslash becomes harmless though unnecessary).
Re: Dynamically Creating (and Calling) Object Methods
by friedo (Prior) on Jan 07, 2007 at 09:04 UTC

    As gaal says, you need to call the per-instance methods by accessing the elements of the hashref. If you want to install them in the class itself, do this:

    foreach my $method (@method_names_from_file) { no strict 'refs'; *{"BigDB::" . $method} = sub { print "it worked!\n" }; }

    That will add a new subroutine to the package's symbol table. Note that this will affect all instances of the class, even ones which were created earlier.

    If you want to have unique, per-instance methods that you can call directly without peeking in the object hash-ref, one way is to use Class::Unique which was written by a really super cool dude. If you want a full-fledged, robust prototype-OO framework, check out Class::Prototyped.

Re: Dynamically Creating (and Calling) Object Methods
by merlyn (Sage) on Jan 07, 2007 at 16:57 UTC