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

I am working on some code for Nagios automations and was curious if there is a way to generalize the following:
sub WriteLine { my $self = shift; my $it = $self->OStream->WriteLine($_[0]); $self->Trace(_CallerStr($it)); return $it; }; sub Select { my $self = shift; my $it = $self->OStream->Select($_[0]); $self->Trace(_CallerStr($it)); return $it; }; sub Add { my $self = shift; my $it = $self->OStream->Add($_[0]); $self->Trace(_CallerStr($it)); return $it; };
To something along the line of:
sub <Call> { my $self = shift; my $it = $self->OStream-><Call>($_[0]); $self->Trace(_CallerStr($it)); return $it; };

Such that, I can use $Wrapper->Remove($item) and the function '<Call>' is executed and <Call> is substituted with 'Remove'. I can only think of using callbacks and a dispatcher but it seems like I would still have to generate all of the callback subs which I kind of want to avoid. Don't get me wrong it's not like copy/pasting is the end of the world, but why copy paste redundancy if there exists a method where I can almost template it? I would also like the simplicity in $X->Y->Z($item) and not have to work a dispatch and sub-call, which I have done before by passing a dispatch function a 'function name' like:

 if(defined $dev_desc{$tcp_data->Dispatch((USF::Modbus::Modbus::CB_NUMBERS))})

But then I end up checking function name and data followed by execution, whereas I want something like (kind of a bad example but the point is there):

 if(defined $dev_desc{$tcp_data->Dispatch->Numbers($tcp_data->Value)})

Maybe an overload? But then I would still have to have overload functions where all I really want to do is have it intuitively fill in the blacks since all of the functions will be doing the same thing since it is essentially a proxy class with subroutine tracing built-in for logging/debugging traceability. If it's not possible then oh well I guess, I just feel like there should be a better solution to excessive redundancy (especially when I am already using a proxy). I also feel it isn't entirely a thing since it would be a nightmare for the interpreter.

Thanks

Replies are listed 'Best First'.
Re: Generic/Variable/Dynamic Subroutines? (Not a redefine)
by haukex (Archbishop) on Aug 18, 2017 at 19:27 UTC

    This will generate those three subs you showed (Update: see Function Templates and Method Names as Strings):

    for my $meth (qw/ WriteLine Select Add /) { my $sub = sub { my $self = shift; my $it = $self->OStream->$meth($_[0]); $self->Trace(_CallerStr($it)); return $it; }; no strict 'refs'; *$meth = $sub; }

    Optionally put that in a BEGIN { ... } block if you want to be able to call the subs without parens.

    Here's a simple wrapper class that uses Autoloading, although autoloading can be a bit dangerous and have some caveats (e.g. the following doesn't overload UNIVERSAL methods like can, although it probably should for completeness; Update: also this example always calls the method in scalar context, although that could be changed by inspecting wantarray; plus obviously wrapper and wrapped objects have completely different classes, so isa and especially ref checks would break), so I would recommend the above solution instead. Use this like my $wrapper = Wrapper->new($object); and you should be able to call the original methods from $object on $wrapper instead. Update 2: I guess I should be more clear that the following is really just a demonstration of the (sometimes scary) power of AUTOLOADing and not something I'd recommend for wide use. You might want to look at something like around from Class::Method::Modifiers instead.

    package Wrapper; use Carp; sub new { my ($class,$wrapped) = @_; return bless \$wrapped, $class; } sub AUTOLOAD { my $wrapped = shift; (my $meth = our $AUTOLOAD) =~ s/.*:://; croak "Wrapped object $$wrapped doesn't have a method $meth" unless $$wrapped->can($meth); # do stuff here my $returnval = $$wrapped->$meth(@_); # call wrapped method # and more stuff here return $returnval; } sub DESTROY {} # don't autoload DESTROY
Re: Generic/Variable/Dynamic Subroutines? (Not a redefine)
by shmem (Chancellor) on Aug 18, 2017 at 19:28 UTC

    Just pass the method into the sub:

    sub Call { my $self = shift; my $method = shift; my $it = $self->OStream->$method($_[0]); $self->Trace(_CallerStr($it)); return $it; };

    If you want to call the methods by their name without them being defined, there is AUTOLOAD:

    our $AUTOLOAD; # needs to be a package global sub AUTOLOAD { $AUTOLOAD =~ s/.*:://; # strip package name return if $AUTOLOAD eq 'DESTROY'; splice @_, 1, 0, $AUTOLOAD; # package/object, method, rest of args goto \&Call; }
    perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
Re: Generic/Variable/Dynamic Subroutines? (Not a redefine)
by LanX (Saint) on Aug 19, 2017 at 19:00 UTC
    Haukex and shmem already gave you the right answer, but I think it might be of interest to explain what happens

    for my $meth (qw/ WriteLine Select Add /) { my $sub = sub { my $self = shift; my $it = $self->OStream->$meth($_[0]); $self->Trace(_CallerStr($it)); return $it; }; no strict 'refs'; *$meth = $sub; }

    In this loop $meth and $sub form a so called closure, each instance of $sub is "closing over" a new instance of the lexical var $meth with a string.

    This my $it = $self->OStream->$meth($_[0]); takes advantage of the fact that method calls are (contrary to sub calls) late evaluated strings, i.e. resolved at runtime. (for completeness $meth could also be a code_ref but that's not relevant here)

    This

    no strict 'refs'; *$meth = $sub;

    is assigning a code_ref in $sub to the symbol named in $meth via a so called *glob.

    That's how the STASH (Symbol Table hASH) works, each varname in a package has a hash key and the value is a "typeglob" with 6 so called "slots". Assigning a code_ref fills the CODE-slot, you can also address other slots like SCALAR, HASH or ARRAY and so on.

    This case showed how to fill the %main:: stash, other namespaces need to be provided explicitly.

    no strict 'refs' was necessary because using symbolic references like in *$meth (i.e. $meth is a string not a pointer like reference) is forbidden under strict.

    This would work right away ...

    *pack::name = sub {};

    ... but your name has to be dynamic.

    Cheers Rolf
    (addicted to the Perl Programming Language and ☆☆☆☆ :)
    Je suis Charlie!

      Thanks for the explanation, I am relatively new to perl and my initial project was only reworking/enhancing existing code. Would you be able to elaborate, or comment, on the benefits of using autosplit for autoload, in that, placing any functions to be called by autoload after __END__ and having autosplit generate the *.al files necessary versus just having the functions inside of the package and having the appropriate method called during autoload? I was able to get both styles detailed, by shmem and Haukex, functional but didn't see any benefit from generating & using the *.al files, but found the simplest implementation was that detailed by shmem.
        > Would you be able to elaborate, or comment, on the benefits of using autosplit for autoload

        If you are loading code from a slow disk autosplit is faster.

        This used to be helpful 20 years ago. ...

        Update

        If compiling Perl code is slow, just autoloading on demand is faster

        Again, this used to be helpful 20 years ago. ... ;)

        Cheers Rolf
        (addicted to the Perl Programming Language and ☆☆☆☆ :)
        Je suis Charlie!