Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

Using SUPER in dynamically generated subs

by clinton (Priest)
on Nov 13, 2006 at 17:16 UTC ( [id://583762]=perlmeditation: print w/replies, xml ) Need Help??

I was trying to generate a sub which relied on the deep binding of a lexical variable, and tried to call $self->SUPER::method() as well:

.... my ($class,$alias_for) = @_; install_sub( $class, 'find_ids', sub { my ($class,$params) = @_; do_something_with($alias_for); # $alias_for is a lexical which defined outside this sub # which uses deep binding so that it is available # when this sub is called return $class->SUPER::find_ids($params); }); ....

The problem with this, is that SUPER refers to the @ISA of the class it is COMPILED into, not the class of $self at runtime. (See Overridden Methods)

So my other option was doing this:

eval "package $class; sub find_ids { ..... }";

...but this wouldn't work either, because then $alias for wouldn't be defined, and I'm relying on the deep binding of lexical variables to have $alias_for defined.

UPDATE See Re^4: Using SUPER in dynamically generated subs for correct usage of SUPER with an anonymous sub

diotalevi suggested using the module SUPER, but this also relies on the package at compile time. However the docs in there did point me to perltoot which says:

The can() method, called against that object or class, reports back whether its string argument is a callable method name in that class. In fact, it gives you back a function reference to that method

So this is my final code, which works:

.... my ($class,$alias_for) = @_; # Store the original find_ids here my $original_find_ids = $class->can('find_ids') or die "Super-classes of $class don't define sub find_ids"; install_sub( $class, 'find_ids', sub { my ($class,$params) = @_; # Do something with $alias_for; # Use the original find_ids as a function # $class as its first argument, so it thinks # that it has been called as a method return $original_find_ids->($class,$params); }); ....

It works for me. Is there a better way? Anything I'm missing?

CAVEAT This will not take notice of any changes to @ISA after this sub has been defined.

UPDATE See Re^6: Using SUPER in dynamically generated subs for benchmarks comparing the three methods mentioned in this thread.

Replies are listed 'Best First'.
Re: Using SUPER in dynamically generated subs
by diotalevi (Canon) on Nov 13, 2006 at 17:21 UTC

    You are wrong. The SUPER module on CPAN does not depend on what package was used at compile time. That is the point to the module. The SUPER module is not the same thing as SUPER:: that is listed in your perl documentation. You can't read perlobj (and related) and know what SUPER does.

    ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

      According to the docs for SUPER:

      Note: you must have the appropriate package declaration in place for this to work. That is, you must have compiled the method in which you use this function in the package from which you want to use it. Them's the breaks with Perl 5.

      And when i tried it, it didn't work.

        There is more than one way to use SUPER than the super() function. super() happens to be nice to look at but does suffer from that drawback. Use the $self->SUPER and $self->super methods instead. Those *don't* suffer from that problem.

        ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

Re: Using SUPER in dynamically generated subs
by ikegami (Patriarch) on Nov 13, 2006 at 17:27 UTC
    To fix your problem, execute the eval when calling install_sub, not in the function called by install_sub. It's even more efficient (since it doesn't call eval everytime the built sub is called)!
    ... my ($class, $alias_for) = @_; my $sub = eval "package $class;" . <<'__EOI__'; sub { my ($class, $params) = @_; do_something_with($alias_for); return $class->SUPER::find_ids($params); } __EOI__ die $@ if $@; install_sub( $class, 'find_ids', $sub ); ...
      When I tried this

      my ($class,$alias_for) = @_; eval "package $class;"<<'SUB'; sub find_ids { my ($class,$params) = @_; # Do something with $alias_for return SUPER::find_ids->($class,\%params); }); SUB

      it gives me the message:

      Variable "$alias_for" will not stay shared at ...

        ( Sorry, this post is completely wrong. )

        That's a different problem, relating to threads and shared variables.

        Is $alias_for a shared variable? I'm no threading expert, but it makes sense that captures do not remain shared. Perhaps you want to capture a reference to the shared variable instead of capturing the shared variable itself.

        my $alias_for_ref = \$alias_for; eval "package $class;"<<'SUB'; sub find_ids { my ($class,$params) = @_; # Do something with ${$alias_for_ref} return SUPER::find_ids->($class,\%params); }); SUB
Re: Using SUPER in dynamically generated subs
by merlyn (Sage) on Nov 13, 2006 at 22:55 UTC
    The problem with this, is that SUPER refers to the @ISA of the class it is COMPILED into, not the class of $self at runtime.
    I'm not sure why you think this is "a problem".

    This is the only sensible way for it to work.

    You don't want to find the same named method in the superclasses of the object. You must find the same named method in the superclasses of the class in which you are compiled.

    Consider X @ISA Y @ISA Z. Now, call method doit on an X, which isn't found, so we go up to class Y. In Y::doit, we want to call SUPER::doit. We cannot look at the @ISA of the object (which is an X), because that'll come right back around to us again! We must look in the @ISA of the class to which we belong (in this case, Y::doit), yielding a potential call to Z::doit.

    So, if I'm following what you're trying to do, you'll be making classes that cannot be subclassed properly, because they'll be looking at the class of the object to get parents, not the "class" of the method. Broken.

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

      I'm not sure why you think this is "a problem".

      This is the only sensible way for it to work.

      Was that sarcasm? The current algorithm has some bizarre fetish for static code, which is a bit odd to encounter in a dynamic language with late binding everywhere else. It makes calling the parent method from methods defined in roles very difficult, for example.

      A working algorithm would search for a possible parent method in the list of MRO candidates after the current point of call. Note that this is sort of pretty much exactly like SUPER does.

      The problem is that I want to dynamically generate a sub that would APPEAR to have been compiled into package X (and so inherit package X's @ISA), even though it was actually generated in package Y.

      But my anonymous sub also relied on the state of the variable $alias_for at compile time. And when I tried a string-eval on the sub, it complained that $alias_for would not stay shared, ie you can have your cake or eat it, not both...

      Thanks to ikegami for showing me how to have both.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others drinking their drinks and smoking their pipes about the Monastery: (5)
As of 2024-04-19 03:22 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found