Slash Boxes
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

AndyArmstrong (7200)

  (email not shown publicly)
Saturday January 19, 2008
05:19 PM

It feels good to get rid of those four lines

[ #35430 ]

This has bothered me far more than it should. I quite often end up writing test code that looks like this:

    my @log = ();

    no warnings 'redefine';
    *Some::Package::some_func = sub { push @log, [@_] };

    sub get_log {
        my @old_log = @log;
        @log = ();
        return @old_log;

# Then some tests that call subroutines that end up calling some_func.

The idea is to monkeypatch (Perl word of the week) the module I'm testing so I can make sure it calls some_func with the right arguments. For example with TextMate::JumpTo I wanted to test that it was going to invoke /usr/bin/open with the correct args - without actually going through with it and opening something. I didn't want the tests to spew a bunch of random windows into the user's text editor.

None of that is what bothers me. What I find troubling is that get_log is five lines of code just to read and reset the contents of a list. That's really quite upsetting, right?

Today I decided I could endure it no longer (I may be being a little theatrical here) so I spent some time thinking about it. Ideally I'd like to do away with the temporary variable altogether but I can't see a way to do that. Instead I've settled for this:

    sub get_log { ( my @got, @log ) = @log }

It's not especially pretty but it's better. That temporary still bugs me though...

Ads by Boggle
Obsessive? Compulsive? Need Help?
No expensive therapy! No painful brain surgery! You can help yourself. Just step away from the keyboard, take a walk, smell the fresh air. YOU ARE FREE!

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
More | Login | Reply
Loading... please wait.
  • I think this will do it:

    sub get_log { return splice @log }

    Just make sure you use it in list context, though.

    -- dagolden

    • Lovely!

      You don't even need the return:

      sub get_log { splice @log }

      That is a hack of great beauty. Thanks David :)

      • I only stuck the return in because I thought you were a Perl::Critic kinda guy. ;-)

        -- dagolden
        • I have mixed feelings about Perl::Critic. In any event, today I'm a do whatever feels good kinda guy :)
          • Perl::Critic is a Perl "advisor" for me. I look at what it spits out and decide on my own if I want to "conform". I have PBP on my desk so I can read up on why it wants me to do it a certain way.
  • This isn’t better, but in the spirit of TMTOWTDI and of the select idiom from perldoc -q unbuffer

    sub get_log { @{ ( [@log], @log=() )[0] } }

    Or to do away with the array-copy, assuming you have set up with “my $log = []” instead:

    sub get_log { @{ ( \@$log, $log=[] )[0] } }

    The \@$ construction is there to force Perl to make a new copy of the array reference in $log. That way the first list element is a copy of $log instead of an alias, and therefore unaffected by the overwrit

  • You can also do this with delete instead of splice:

    sub get_log { delete @log[0 .. $#log] }

    It's slightly slower than splice but you may consider it's closer to documenting your intent.

  • It does it all for you, and provides a nice interface to grabbing function arguments.