It feels good to get rid of those four lines

brian_d_foy on 2008-01-20T06:38:00

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!


splice?

dagolden on 2008-01-19T22:58:32

I think this will do it:

sub get_log { return splice @log }

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

-- dagolden

Re:splice?

AndyArmstrong on 2008-01-19T23:05:16

Lovely!

You don't even need the return:

sub get_log { splice @log }

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

Re:splice?

dagolden on 2008-01-19T23:44:40

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

-- dagolden

Re:splice?

AndyArmstrong on 2008-01-20T00:26:21

I have mixed feelings about Perl::Critic. In any event, today I'm a do whatever feels good kinda guy :)

Re:splice?

sigzero on 2008-01-21T15:08:50

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.

A different solution

Aristotle on 2008-01-20T06:01:09

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 overwriting of $log in the next expression.

But the splice solution is lightyears ahead.

delete Works Too

Smylers on 2008-01-20T07:47:17

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.

Re:delete Works Too

AndyArmstrong on 2008-01-20T11:32:36

We better not let any civilians see this thread otherwise Perl will get a bad name :)

Check out Test::Resub

nmueller on 2008-01-21T03:51:19

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