Data::Printer has a problem with return values. By default, it tries to be smart for the user, so if you do:
it will dump the data to STDERR. But if you do:
use DDP; my $ret = p $data;
it will return the dump instead of printing it. This also let's you do stuff like:
in case you want the dump to go to STDOUT instead of STDERR. Of course, you can also achieve this
by doing something like:
use DDP output => 'stdout'; p $data;
But I digress.
The problem is that, to detect whether the user wants us to return something or not, we rely
on Perl's "wantarray" function. This is all nice and well, except if you look at wantarray's documentation
you'll see a minor catch:
"wantarray()"'s result is unspecified in the top level of a
file, in a "BEGIN", "UNITCHECK", "CHECK", "INIT" or "END"
block, or in a "DESTROY" method.
This means that if you're debugging stuff in BEGIN, DESTROY, etc, you might do something like:
and get absolutely nothing. The reverse would also happen, where you could do:
use DDP; my $str = p $data;
and see it printed on STDERR instead of returned. Of course, since you'll see the data on the screen,
this is less annoying than the former.
I have received some reports that p($data)
"wasn't working" meaning it wasn't printing anything on the screen,
and after a lot of debugging I think wantarray
's odd behavior might be the cause - specially since the user was within a BEGIN block.
Another huge annoyance where Data::Printer doesn't DWIM is when it's the very last statement of your function.
Let's say you have this:
sub do_something {
...
}
my $data = do_something();
and you're debugging the do_something()
function, so you want to call p()
to see your data before you
continue coding. If you did something like:
my $data = do_something();
use DDP; p $data;
then you'd be just fine. The problem lies when you try to mangle inside do_something(). And why
shouldn't you? I mean, you're debugging, right? So you do something like:
sub do_something {
...
use DDP; p $struct;
}
my $data = do_something;
and you get nothing on the output. WTF?
Did you spot what happended?
If you check the docs, you'll see mst warned about this months ago (but who wants
to look at the docs? It's a tool to print data structures. You want it to work, dammit!)
The problem here is that you want something from do_something(), and since you called
p() as the last statement, you basically told perl that do_something() should return
whatever it gets from p(), meaning now p() is NOT in void context, so it returns
the dump instead of printing it on the screen.
If you had made sure p() wasn't the last statement in the function ( p $struct; 1; ) or
if you had called do_something() in void context, or if you had set DDP with the 'pass' return
value (which triggers the pass-through behavior in which p() will always print and
return the value of the actual variable, not the dump string), then everything would have
worked just fine.
Except you didn't, and now you're debugging the debugging tool. Not cool.
At first I thought it wasn't that big a deal, that it was a very specific debugging scenario
and the user would likely known what it was doing. So as long as it was documented,
everything would be fine. Turns out it's not that specific at all, and
the very last thing you want when debugging code is to be bitten by your debugging tool.
I clearly had made a poor design decision, and now that Data::Printer is being used all over (thanks, guys!)
I feel like you should help me out with making the right decision this time, if you want/care.
What should I do? What should Data::Printer do?
Leon Timmermans already helped by pointing out a way to query caller() and see if we're inside the
BEGIN, INIT, etc, blocks, but this doesn't solve the latter issue, which can be even more annoying.
As I see it (and I'm VERY open to other ideas) I have a few ways to "fix" this:
=> Leave it as it is, and add a bigger note on such behavior. I don't like this one, otherwise
I wouldn't be having this argument. But I'm adding it just for thorougness' sake.
=> make p() pass-through by default. This will make p() print the dump to STDERR always
unless you do something about it, where something is:
use DDP return_value => 'dump'; # go back to the (now, so far) default behavior
use DDP output => \$var; # append the dump to $var
use DDP output => 'stdout'; # use STDOUT instead of STDERR
If we change the default behavior to that, it means that if you ever did something like:
or
You'll get $data dumped to STDERR and will print whatever is in $data
back to STDOUT (via the print()
call).
It also means that if you wanted the dump string back, you'd have to change return_value
back to "dump" or
point the output
property to a scalar reference, like in the first two examples above.
Do people really use the return values? I think they can be quite useful for something like printing to a
debugging webpage, or maybe a log file.
Which brings me to solution #2:
=> Change p()'s default return_value behavior to 'pass' just like in #1, but also exporting a d() function
that would be like p() except always returning the string instead of printing, just like Data::Dumper does.
The reason I'm not in love with this idea is because I don't want to pollute the caller's namespace with
more functions - one should be enough I guess.
We could of course add an alias for d() like we do for p(), or even make the exporting optional. But if the
user has to adjust his/hers "use DDP
" parameters, then why can't he/she simply make the changes above?
Which brings me to potential solution #3:
=> Change p()'s default return_value behavior to 'pass' just like in #1, but create a new package called,
say, DDX (heck, we could even use DDP itself so it wouldn't be just an alias do Data::Printer), that
would export 2 functions to the user's namespace (and not just one, as Data::Printer would):
p() - always prints stuff
d() - always returns the string
This could even be enough to fix another annoyance to more advanced Data::Printer users: prototypes.
p() - always prints stuff, uses prototypes
np() - always prints stuff, no prototypes
d() - always returns the string, uses prototypes
Does it look sane-ish to you? Not really? Alternatives?
It could be the case of creating solution #4 in which Data::Printer's p() won't change its default
behavior, but DDP's p() will. But I'm not sure I want to maintain this sort of thing, so I'm more inclined to #2 or #3.
Of course, I'd have to make sure all other customization options are in sync with whatever is chosen
as solution.
If you care and/or have any suggestions, please add a comment below. I'm going to think about this for the next few days and make a decision that hopefully will make Data::Printer an even more reliable tool, specially for beginners.