syphilis has asked for the wisdom of the Perl Monks concerning the following question:

Hi

As I understand it, use overload '""'=> \&my_quote; will overload both "" and print.

That is, with both:

$x = "$blessed_obj";
and
print $blessed_obj;

the my_quote subroutine will be called, and the return value assigned to $x (in the first instance) and printed to stdout (in the second instance).

Normally there's no problem in having the same sub serve both "" and print as they both usually require the same processing. However, I have a situation where it would be handy if different subs could be called for "" and print - ie they require *differing* processing.

Is there some way of overloading print and "" with different (separate) subs ?
If not - is there some way that my_quote would be able to determine whether it has been called via the overloading of print or via the overloading of "" ?

My feeling is that the answer to both those questions is "no" ... but I've found in the past (on more than one occasion) that "feeling" is not always a reliable indicator wrt perl.

Cheers,
Rob

Replies are listed 'Best First'.
Re: Overloading print()
by jbert (Priest) on Sep 05, 2007 at 08:00 UTC
    I don't know of a way to accomplish what you need, but I'd question the readability of this.

    Reading code, I'd expect:

    print $blessed_obj;
    and
    print "$blessed_obj";
    to do the same thing.

    Overloading can be very convenient, but too much magic can hurt maintainability.

    Of course, it's always good to learn, so could you perhaps share the situation you're in where it would be handy for these two to behave differently?

      ... could you perhaps share the situation you're in where it would be handy for these two to behave differently?

      Firstly, I see what you mean by the expectation that print $blessed_obj; and print "$blessed_obj"; should do the same thing. (A nicely insightful observation :-)

      My question came about this way:
      In C, users of the mpfr library will output values to stdout using the mpfr_out_str function - which prints directly to stdout (rather than return a string which one can then print to stdout). As regards Math::MPFR (which wraps the mpfr library function), I thought "Wouldn't it be nice if print was overloaded to use mpfr_out_str ... then C and Perl are guaranteed of producing the same output." But then, that would mean that every time someone did $x = "$blessed_obj"; the value encapsulated in $blessed_obj is going to be printed to stdout ... and that's probably not what's intended :-)

      Anyway, as you've just demonstrated, there's a very good reason that print and "" should be overloaded by the same function ... so I think that's the end of that train of thought. Probably better to have print and "" overloaded by a function that returns a string that emulates the mpfr_out_str output - as is currently the case. Only problem there is that, since the mpfr library itself doesn't provide such a function, I have to hand craft my own ... and therein lies the potential for discrepancies.

      Thanks jbert.

      Cheers,
      Rob
        Why don't you make Math::MPFR capture the output to STDOUT by localizing *STDOUT in the wrapper functions and redirecting it to a string?

        Then you can return the strings instead of printing them, and produce all output with perl's print.

        Or do you do that already?

        Cool, thanks for the explanation.

        So...it sounds as though the problem is problematic API. So the *real* fix is to refactor the underlying MPFR "output string" func to call a 'format to string' helper function, which you can then use for your stringification.

        I appreciate that might not be easy/desirable/practical (mimicing the func in perl is probably easier), but hey.

        "Wouldn't it be nice if print was overloaded to use mpfr_out_str" ... But then, that would mean that every time someone did $x = "$blessed_obj"; the value encapsulated in $blessed_obj is going to be printed to stdout

        mpfr_get_str() probably does what you want here. If so, I'd wager that Math::MPFR already does what you want, since it exposes Rmpfr_get_str() and, using it, already overloads its objects' stringification.

        -pilcrow
Re: Overloading print()
by Anno (Deacon) on Sep 05, 2007 at 17:15 UTC
    As I understand it, use overload '""'=> \&my_quote; will overload both "" and print

    That isn't quite right. The print operator as such cannot be overloaded. Since print always stringifies its arguments, you see the effect of overloaded '""' whenever such an object is printed.

    If you need special processing on an object, provide a method special (using a more reasonable name) and call it when printing

    print $obj->special, "\n";
    Nothing new here, of course.

    On an unrelated note, there is another pitfall built into '""' overloading. If you allow autogeneration of overload methods (which is the default), overload will autogenerate a method for overloading of 'bool' based on stringification. That may come as a surprize if you expect objects to always represent a true value as is normally the case. I routinely "counter-overload" 'bool' as

    use overload ( '""' => 'stringify', # or whatever bool => sub { 1 }, # ... );
    Alternatively switch off autogeneration altogether.

    Anno

      Since print always stringifies its arguments, you see the effect of overloaded '""' whenever such an object is printed

      Aaah ... I *now* see the connection. Thanks for the clarification.

      Interesting that you mentioned 'bool'. I hadn't overloaded it so, as you pointed out, any boolean evaluations were based on what the overloaded "" returned. I was aware that was happening - and it was pretty much doing what I wanted (namely, returning true unless the string was "0"). It then occurred to me (only a couple of days ago) that nans and -0 would also return true - since the strings "@NaN@" and "-0" return true. That's not what I wanted, so I've now explicitly overloaded bool to behave as I want.

      Cheers,
      Rob
Re: Overloading print()
by moritz (Cardinal) on Sep 05, 2007 at 08:19 UTC
    Just out of curiosity: which situation requires such a behavior?

    Perhaps you could somehow distinguish between scalar and list context, because print propagates list context.

    But in use overload '""' => sub { wantarray ? 1 : 0 }; a simple print still produces a 0.

    I experimented a bit with caller, but it doesn't seem to treat print as an ordinary sub.

    Maybe you could overwrite CORE::print with a sub that either appears in caller, or that somehow influences coercion?

    Actually I have no idea how to do that, I'm just thinking aloud...

Re: Overloading print()
by almut (Canon) on Sep 05, 2007 at 17:09 UTC

    Hi Rob,

    from reading the rest of this thread, I figure your real problem is to capture the stdout of an XS routine.

    The key thing to note here is that, within the C routine, stdout corresponds to fileno 1. So, in your Perl code, you'll need to arrange for things to read the output written to exactly that filehandle, e.g. by setting up a pipe to it.

    This a bit more involved than you might think, but the following should do the trick (to keep the example concise, I'm using Inline here, but it should equally work with regular XS code):

    #!/usr/bin/perl use Inline C => <<'EOC'; void foo() { printf("something being sent to stdout\n"); fflush(stdout); } EOC my $output; { # save original STDOUT open my $saved_stdout, ">&STDOUT"; # create a pipe, which we'll use to read from our own STDOUT local(*RH, *WH); pipe RH, WH; # connect the writing side of the pipe to STDOUT, with # STDOUT being (and remaining) fileno 1 (!) open STDOUT, ">&WH" or die "open: $!"; # debug: verify that fileno really is 1 printf STDERR "fileno(STDOUT): %d\n", fileno(STDOUT); # call the C/XS function whose stdout we want to capture foo(); # close WH to avoid buffering issues (pipes are buffered) close WH; # read output (one line) $output = <RH>; close RH; # restore original STDOUT open STDOUT, ">&", $saved_stdout or die "open: $!"; } print "output of foo(): $output";

    prints:

    fileno(STDOUT): 1 output of foo(): something being sent to stdout

    The crucial thing is that STDOUT has to correspond to fileno 1, so localising STDOUT will not work here (local *STDOUT will result in a new fileno...). For the same reason, you can't use "open-to-a-string", because in that case the filehandle will be a Perl internal one (fileno -1), which the C side knows nothing about...

    With the above code, the open STDOUT, ">&WH" is doing a dup2 system call (among other things), which (in this particular case) will first close STDOUT and then immediately reassign that same fileno, which is 1. BTW, this works only if you haven't messed with Perl's STDOUT before (by default, it is fileno 1).

    To get a deeper understanding of the mechanics under the hood, you might want to play around with tracing system calls (i.e. strace and friends).  HTH.

      almut++ ... a truly edifying piece of code which does exactly what I was after.

      However, looking at the hoops it jumps through, I start to wonder about its portability and reliability. (Could it potentially create more problems for me than it solves ?)

      And I couldn't work out how to restore the original stdout on perl 5.6 (where ">&" is not understood) - though that's not really a major consideration.

      So ... I'm not yet sure whether I'll implement that approach, but thanks for going to the trouble of presenting such a well written and useful demo.

      Math::MPFR already overloads "". The version on CPAN doesn't render inf, nan, and -0 as it should, though that's taken care of in the next release. Apart from that, the only difference (I'm aware of) between the overloaded "" and mpfr_out_str is that mpfr_out_str doesn't strip trailing zeros, whereas perl *does* strip them. (That is, whereas mpfr_out_str might render a number as  1.23450000e1, the overloaded "" will return 1.2345e1.) I don't see any need to be concerned about that discrepancy. The appeal of having the overloaded "" wrap mpfr_out_str is simply that I can then be certain that the outputs will match ... without even having to consider the many-and-varied "what if" scenarios.

      Cheers,
      Rob

      I believe that this approach will "hang" ("deadlock", "lock up") if the XS routine tries to output more than one "system buffer" full of data. Exceeding somewhere on the order of 4KB of data to the pipe will cause the XS code to hang, waiting for someone to read from the pipe, draining it to make room for the extra data.

      - tye