Stories
Slash Boxes
Comments
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 ]

Ovid (2709)

Ovid
  (email not shown publicly)
http://publius-ovidius.livejournal.com/
AOL IM: ovidperl (Add Buddy, Send Message)

Stuff with the Perl Foundation. A couple of patches in the Perl core. A few CPAN modules. That about sums it up.

Journal of Ovid (2709)

Tuesday May 26, 2009
10:10 AM

Debugging In List Context

[ #39036 ]

Today I faced a bug in this code:

$self->set_build_data([
        $self->build_primary_element,
        $self->build_identifiers,
        @build_event_in,
        @build_event_of,
        [
            offset => {},
            ($rs->offset ? $rs->offset->in_seconds : undef)
        ],
        [ position => {}, $rs->position ],
        [ title    => {}, $rs->title ],
        $self->build_synopses,
        $self->build_from('Taggings'),
        $self->build_resultset_links(qw/taggings changes/),
    ],
);

I saw that the build_from('Taggings') method wasn't returning the taggings. I put this code into the build_from method:

sub build_from {
    my ( $self, $type ) = @_;
    $DB::single = 'Taggings' eq $type;

The $DB::single = $some_true_value code sets a breakpoint. When you run the code through a debugger, hitting 'c' for "continue" will run the code up to that point and then halt there, waiting for you to issue additional commands (as a fun exercise, 'ack' for DB::single in your installed CPAN modules). That let me easily step into the code and find out that something prior to that method call had altered the state of the object and prevented my tags from being built. But let's look back at that code again. I didn't relish stepping into every one of those methods and finding out which was the offending code. As it turns out, it's the $self->build_context data which was getting altered. However, I can't just warn on those values because this fails:

use Data::Dumper;
$self->set_build_data([
        $self->build_primary_element,
        warn Dumper($self->build_context),
        $self->build_identifiers,
        warn Dumper($self->build_context),
        @build_event_in,
        warn Dumper($self->build_context),
        ...

It fails because warn returns a true value and thus destroys my list. However, there's an interesting feature of lists which many people are not aware. An empty lists collapses out of a list. The following list has two elements, not three:

my @two_element_list = (1, (), 2);

Now you can just have a subroutine return any empty list and safely make subroutine calls in the middle of a list. However, because of how lists work, you can't rely on line numbers via warn, so you need the __LINE__ directive to get the correct line number. That leads to the following code:

use Data::Dumper::Simple;
my $warn = sub {
    my $line = shift;
    warn Dumper($line, $self->build_context);
    return ();
};
$self->set_build_data([
        $self->build_primary_element,
        $warn->(__LINE__),
        $self->build_identifiers,
        $warn->(__LINE__),
        ...

And near the top of my test output, I see the following:

$line = '35';
$self->build_context = {
  'expand_junction' => bless( [
    'promotions',
    'segments',
    'taggings'
  ], 'Perl6::Junction::Any' ),
  'uri_base' => bless( do{\(my $o = 'http://localhost/')}, 'URI::http' )
};
$line = '42';
$self->build_context = {
  'expand_junction' => bless( [
    'promotions',
    'segments'
  ], 'Perl6::Junction::Any' ),
  'uri_base' => bless( do{\(my $o = 'http://localhost/')}, 'URI::http' )
};
63 at /home/ovid/pips_dev/work/Pips3/branches/exploratory/lib/PIPs/API/Builder/Segment Event.pm line 60.
$self->build_context = {
  'expand_junction' => bless( [
    'promotions',
    'segments'
  ], 'Perl6::Junction::Any' ),
  'uri_base' => bless( do{\(my $o = 'http://localhost/')}, 'URI::http' )
};

And now I can instantly see that I lost the 'taggings' build context right after I called build_identifiers, thus telling me quickly where the bug in my code was.

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.
  • #!/usr/bin/perl

    use strict;
    use warnings;

    my @a = (
                    1,
                    @{[map {()} warn "in here\n"]},
                    2,
                    3
    );

    print map { "$_\n" } @a;

    • or the slightly less stupid (I don't know why I threw it in an arrayref to start with):

      #!/usr/bin/perl

      use strict;
      use warnings;

      my @a = (
                      1,
                      (map {()} warn "in here\n"),
                      2,
                      3
      );

      print map { "$_\n" } @a;

      • An even shorter way to write that is this:

        (map {;} warn "in here\n")

        However, I prefer to take a page from Javascript, by defining the following amusing function:

        sub void {}

        I can then write the code in this much nicer way:

        (void warn "in here\n")

        This is far nicer than Ovid’s approach as well, IMO.

  • return() with no arguments (which is what you're doing) does the "right thing" in both scalar and list contexts. It returns either an empty list or undefined value.

    Thus you could also have written:

    return;

    for the same effect. I'm sure you know this already, but from your commentary and style it did look like you were trying to explicitly return an empty list.

    • Yes, I normally just use a bare return (and get annoyed when people use 'return 0' for false), but I honestly didn't think about that here because I originally had this:

      my $warn = sub { warn Dumper($self->build_context); () };

      That showed the annoying __LINE__ problem and I decided to be explicit about the return when I added the $line variable. Normally about the only time -- aside from this example -- I return an explicit empty list is when I do this:

      return $condition ? $some_value : ();

    • Oh, and what I didn't say, but should have, in my first reply was "thanks". The bare return is (IMHO) a cleaner solution.

    • For less trickery with subs and call stacks and line numbers, remember your old friend 'do'!

      $ perl -le'print join " ", (1, do { warn 2; () }, 3)'
      2 at -e line 1.
      1 3