Stories
Slash Boxes
Comments

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

Aristotle (5147)

Aristotle
  pagaltzis@gmx.de
http://plasmasturm.org/

Blah blah blah blah blah [technorati.com]

Journal of Aristotle (5147)

Sunday July 09, 2006
05:16 AM

Intelligently shortcutting function maker

[ #30226 ]

I often want to write short utility functions that do things like running a simple but frequently needed substitution on strings or the like. For these functions, I often want shortcutting behaviour: operate on $_ if not given a parameter, and operate destructively in-place if not asked to return values.

Writing functions that work like this is a real pain in the neck: there are multiple cases to be considered, you have to be careful to preserve the aliasing nature of @_ and $_, etc. It‘s way too much work to keep doing it over and over.

So here’s a function maker that lets you build such functions, carefully written to rely entirely on aliasing, rather than jumping through any reference-taking hoops. It expects to be given a reference to a function which always operates destructively on its parameters (ie modifies the elements of @_), and from this builds a function that will default to $_ as its input and will either return modified copies or modify its operands in-place.

Eg.:

BEGIN { *basename = shortcutted { s!.*/!! for @_ }; }

for( '/path/to/foo', '/some/path/to/bar' ) {
    print "Munging " . basename . "\n";
    open my $fh, '<', $_ or die $!;
    # note how $_ still contains the full pathname
    # ...
}

The code:

sub shortcutted(&) {
    my $sub = shift;
    sub {
        my @byval;
        my $nondestructive = defined wantarray;
        $sub->( $nondestructive
            ? ( @byval = @_ ? @_ : $_ )
            : (          @_ ? @_ : $_ )
        );
        return $nondestructive ? @byval[ 0 .. $#byval ] : ();
    };
}

Tests:

use Test::More;

sub original() { 'original' }
sub modified() { 'modified' }

my $test = shortcutted { $_ = modified for @_ };

plan tests => my $num_tests;

{
    local $_ = original;
    $test->();
    is( $_, modified, 'in-place on $_' );
    BEGIN { $num_tests += 1 }
}

{
    local $_ = original;
    my $res = $test->();
    is( $_,   original, 'nondestructive from $_' );
    is( $res, modified, '...returned correctly' );
    BEGIN { $num_tests += 2 }
}

{
    my $num = 10;
    my @original = ( original ) x $num;
    my @modified = ( modified ) x $num;
    $test->( my @data = @original );
    is_deeply( \@data, \@modified, 'in-place on params' );
    BEGIN { $num_tests += 1 }
}

{
    my $num = 10;
    my @original = ( original ) x $num;
    my @modified = ( modified ) x $num;
    my @res = $test->( my @data = @original );
    is_deeply( \@data, \@original, 'non-destructive from params' );
    is_deeply( \@res,  \@modified, '...returned correctly' );
    BEGIN { $num_tests += 2 }
}

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • That's really useful. But having to use coderefs is a bit ugly. So I've added support for a :Shortcut attribute. The code is in my svn repository: Attribute-Shortcut/ [happygiraffe.net].

      sub basename :Shortcut { s!.*/!! for @_ }

      for ( '/path/to/foo', '/some/path/to/bar' ) {
          print "Munging " . basename . "\n";
          open my $fh, '<', $_ or die $!;
          # note how $_ still contains the full pathname
          # ...
      }

    -Dom

    • I wish I'd thought of that. Very clever!

    • Nice. I guess it’s worthy of being put on CPAN, after all. I’ll steal the attribute idea as that makes it much easier to use with named subs, but I still want the subref interface available for building anonymous ones.

      Now I just need to think of a good name – shortcutted is fine for an off-the-cuff function, but it doesn’t meet my standards for something on CPAN…

      • Feel free to steal the code as you see fit.

        As to still wanting to do Subrefs, I think that you should be able to use Attributes with them as well. But I have no idea how, I'll need to play with that.

        -Dom