Search code examples
perl

Perl v5.26x attributes like decorators/overlays. Subs pre and post processing


I'm trying to understand Perl attributes for subs. For example, I expected to got as output:

before
middle
after
125

But, I have:

before
middle
after
middle
124

How to use attributes as overlays for main sub?

use Attribute::Handlers ;
use Data::Dumper ;

sub decorator( $\*&\@\@$$$ ):ATTR {
    my ( $package , $symbol, $referent, $attr , $data, $phase, $filename, $linenum ) = @_ ;
    my ( $before , $after ) = @$data ;

    # before processing
    $before->( @_ ) ;

    # decorated sub
    my $result = $referent->( ) ;

    # after processing
    $after->( $result )
}

sub c( $ ):decorator(
    # before
    sub( $\*&\@\@$$$ ) {
        warn 'before' ;
    } ,
    # after
    sub( $ ) {
        warn 'after' ;

        shift( @_ ) + 1
    }
) {
    warn 'middle' ;

    splice( @_ , 1 ) + 1
}

print( __PACKAGE__->c( 123 ) ) ;

For admins. Please, stop moking!


Solution

  • The key piece of information you're missing is that decorator is called once at compile time.

    decorator will need to replace &c with a sub wrapping the true &c to get the desired result.

    use Attribute::Handlers;
    use Sub::Name qw( subname );
    
    sub Wrap :ATTR(CODE) {
       my ( $pkg, $sym, $old_sub, $attr, $data, $phase, $fn, $ln ) = @_;
       my ( $before, $after ) = @$data;
    
       my $name = *{ $sym }{ NAME };
     
       my $new_sub = subname "wrapped_$name", sub {
          wantarray
          ? $after->(         $old_sub->( $before->( @_ ) )   )
          : $after->( scalar( $old_sub->( $before->( @_ ) ) ) )
       };
    
       no warnings qw( redefine );
       *$sym = $new_sub;
    }
    

    Tested:

    sub f :Wrap(
        sub { $_[0], $_[1]+50 },
        sub { $_[0]+7000 },
    ) {
        $_[1]+600
    }
    
    say __PACKAGE__->f( 4 );  # 7654
    

    Note: This uses the calling convention from the OP's own answer, which differs from the calling convention in the the OP's question.