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

So, I thought I had a brainwave....

I generally don't bother with FH type filehandles, but open $FH, ...., and use $FH instead.

Problem is handling STDERR etc. the same way.

Simple approach is to simply do:

  $STDERR = *STDERR{IO} ;

at some early stage, and use $STDERR thereafter. I do this in a module along with my other favourite bits and pieces, and export $STDERR et al.

I just read that exporting variables is a Bad Thing. So I tried another approach. I tried creating a STDERR subroutine, which returns *STDERR. Stripping down to the basics, and adding a little test stuff, this looks something like:

use strict ; use warnings ; sub STDERR { *STDERR } ; p(STDERR, "---Hello world: '", STDERR, "'\n") ; print STDERR "+++STDERR: '", STDERR, "'\n" ; sub p { my ($H, @p) = @_ ; print $H "//[$H] ", @p ; } ;

and the result is, STDERR:

  //[*main::STDERR] ---Hello world: '*main::STDERR'

and, STDOUT:

  *main::STDERR

Which indicates that STDERR() is doing what was hoped for. So far so good. The sad part is that STDERR is no longer working as a filehandle ! At least as far as print is concerned.

What's really odd is that the parser appears to have accepted the print STDERR as a filehandle -- for if not, there would have been a lot of compile time complaints about possible missing operator ! However, something clearly goes wrong -- and there are no errors or warnings raised. Quite independently of whether this trick "should" work, the current behaviour looks like a failure of diagnostics at least.

FWIW, it occurred to me that sub STDERR ought to look like a constant, thus:

use strict ; use warnings ; sub STDERR () { *STDERR } ; p(STDERR, "---Hello world: '", STDERR, "'\n") ; print STDERR "+++STDERR: '", STDERR, "'\n" ; sub p { my ($H, @p) = @_ ; print $H "//[$H] ", @p ; } ;

but Perl throws up all over that, as follows:

String found where operator expected at tst.pl line 8, near "STDERR +"+++STDERR: '"" (Do you need to predeclare STDERR?) syntax error at tst.pl line 8, near "STDERR "+++STDERR: '"" Execution of tst.pl aborted due to compilation errors.

...which seems to indicate that I'm upsetting something !

Wadya think ?

Chris

Replies are listed 'Best First'.
Re: sub STDERR { *STDERR } -- nearly works !
by Fletch (Bishop) on Apr 08, 2008 at 18:55 UTC

    If you run your first code through B::Deparse I don't think it's being parsed the way you think it is. But rather than playing games attempting to get something other than a bareword or block working in the filehandle slot, why not just use something along the lines of: open( my $stderr, ">&STDERR" ) or die "Can't dup STDERR: $!\n";?

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

      OK... print STDERR "....", ... is not being parsed as a file handle, but as a subroutine call with no brackets... for some reason I thought that required a prototype :-(

      The second version, with a () prototype clearly stops that happening.

      So the problem is that the subroutine-ness of the modified STDERR is taking precendence over the filehandle-ness -- which is a shame, really.

      There are, of course, any many ways of creating a $XXX version of STDERR... what I was hoping for was a way to tweak a FH so that it can appear equally in: print FH .... and p_sub(FH, ....).

      Because STDERR is visible everywhere, sub STDERR () { *STDERR } ; placed anywhere very nearly -- but not quite :-( -- does the job without every module needing to do, say, my $STDERR=*STDERR{IO} for itself.

      Chris

Re: sub STDERR { *STDERR } -- nearly works !
by alexm (Chaplain) on Apr 08, 2008 at 20:28 UTC
    I guess this works as expected:
    print {STDERR} "+++STDERR: '", STDERR, "'\n" ;

      Not sure I'd bracket 'works as expected' and Perl together :-)

      For completeness, it's being parsed as:

        print STDERR("+++STDERR: '", ....) ; -- predeclared sub STDERR without a prototype.

        print STDERR() "+++STDERR: '", .... ; -- predeclared sub STDERR ()

      where the second is treated as a broken expression involving the return value of STDERR().

      This behaviour is consistent with the general handling of subroutines referenced by a bareword.

      Where a FILEHANDLE may appear the parser has to jump through hoops to allow print $FH ...., which it parses as a FILEHANDLE and leaves as a run-time problem. With an ordinary print FH .... the parser also leaves checking that FH actually refers to something to run-time.

      Having predeclared sub FH () { *FH } ; I can see that the parser is in a bit of a bind when it sees print FH "Hi!";. The prototype says that FH("Hi!") is not the correct interpretation. But it does know that FH is a sub..., even though, on the face of it, it looks just like a filehandle !

      I imagine that generalising print FH .... even further, so that the FH could be an expression that returned a filehandle object, would be a nightmare -- leaving broken code to be found at run-time. However, the special case of a predeclared subroutine with a () prototype might be a small step ?

      The other thing that is special about filehandles is that there is no way to declare them. Suppose one could declare our FH : filehandle ; (say). Now: (a) strict could throw out print SPELING_ERROR ...; and (b) use of FH in an expression could return *FH{IO} implicitly, so some_sub(FH, ....) or $object->{handle} = FH would work just fine ! Of course, STDERR et al could then be predeclared with a 'filehandle' attribute :-)

Re: sub STDERR { *STDERR } -- nearly works !
by ikegami (Patriarch) on Apr 09, 2008 at 01:03 UTC

    The idea with using $FH instead of FH is to avoid messing with other scope, being affected by other scopes, and to ensure timely garbage collection.

    None of those benefits are achieved by using $STDERR instead of STDERR — the variable is global even if you pretend it isn't — so you're needlessly complicating your code. I hope that you will revert to using STDERR now that you figured this parsing puzzle.

      Using $FH also allows the filehandle to be passed around, assigned, stored in other structures and objects, etc... so that it's a first class thing, not a bag on the back.

      I have a collection of "useful widgets" in a module which I use in all my modules and programs. What that currently does is export $STDERR (etc), which is set $STDERR = *main::STDERR{IO}. So my $STDERR is almost as global as STDERR.

      This brings the STDxxx filehandles into line with all other filehandles in my code, sharing all the benefits of being first class variables.

      If the sub STDERR {*STDERR} worked, then it would be implicitly global and no export of a variable would be required. /sigh/

        *STDERR is already a perfectly fine first class variable. No need to duplicate it as $STDERR.

        It's especially curious that you point out that a STDERR function would be even better in the last paragraph, contradicting the rest of your post (which argues for conformity with other file handles).

Re: sub STDERR { *STDERR } -- nearly works !
by ysth (Canon) on Apr 09, 2008 at 03:42 UTC
    I just read that exporting variables is a Bad Thing.
    Huh, what? Care to expand on that? I think someone's sent you on a wild goose chase.

      ...not exporting variables...

      see http://perldoc.perl.org/Exporter.html, at the end under Good Practices:

      "Do not export variable names. Just because Exporter lets you do that, it does not mean you should."

      and:

      "Exporting variables is not a good idea. They can change under the hood, provoking horrible effects at-a-distance, that are too hard to track and to fix. Trust me: they are not worth it."

      This section appears to be new in v5.10.0.

        I read that as saying you shouldn't compound the potential action-at-a-distance evil of having a package variable by allowing it to be referred to in other packages yet not qualified with the original package.

        Which doesn't really apply to something exported from what sounds like a utility package intended to provide a "global" variable.

        But since all packages share the same global $STDERR anyway, trying to export it is indeed a bad thing.