develooper Front page | perl.perl5.porters | Postings from August 2008

Re: Near-FMTEYEWTK instructorial on ties, handles, and methods (was: How to tell whether readline got an error or EOF)

From:
Ben Morrow
Date:
August 2, 2008 21:18
Subject:
Re: Near-FMTEYEWTK instructorial on ties, handles, and methods (was: How to tell whether readline got an error or EOF)
Message ID:
iunfm5-uqc1.ln1@osiris.mauzo.dyndns.org

Quoth tchrist@perl.com (Tom Christiansen):
> > Quoth tchrist@perl.com (Tom Christiansen):
> 
> > [snippety: tied handles vs IO::Handle]
> 
> >> Just because I can say 
> >> 
> >>     print CALC "$sum\n"
> >> 
> >> with the object in dative position, and thanks to the tie, get the sub
> >> Tie::Open2::PRINT invoked on the tied(CALC) object, does *NOT* mean I can
> >> also turn around and call CALC->error() or CALC->clearerr() and expect
> >> *that* to (miraculously) get at the IO::Handle methods of those names.
> >> Why?  Lots of reasons, but for one, it's because *that* compiles into
> >> "CALC"->error(), and there's not even a CALC class to be found, that's why.
> 
> > No, it doesn't; not if there's a *CALC{IO} filehandle in scope and no
> > CALC:: package or &CALC sub. Otherwise STDIN->error() wouldn't work
> > either.
> 
> Oh, you *think* so, do you?  
> 
> I have some sorry news for you.
> 
> But you know, you *really* shouldn't contradict someone without checking
> (read: testing the code) for sure that you're right and them, wrong.  

I apologise for the bluntness. However, I *did* test what I was saying
before I posted; specifically, I tested

    $ perl -MIO::Handle -le'print CALC->error'
    Can't locate object method "error" via package "CALC" (perhaps you
    forgot to load "CALC"?) at -e line 1.
    $ perl -MIO::Handle -le'open CALC, "foo"; print CALC->error'
    -1

and then I tested

    $ perl -MIO::Handle -le'{package Foo; sub error { "FOO" }}
        open CALC, "foo"; bless \*CALC, "Foo"; print CALC->error'
    -1
    $ perl -MIO::Handle -le'{package Foo; sub error { "FOO" }}
        open CALC, "foo"; bless *CALC{IO}, "Foo"; print CALC->error'
    FOO

and it was a good thing I did, as I thought to start with the first
would do the trick. It also works with tie:

    $ perl -MIO::Handle -le'
        {package Foo; sub TIEHANDLE { bless [] }}
        tie *CALC, "Foo"; print CALC->error'
    -1
    $ perl -MIO::Handle -le'
        {package Foo; sub TIEHANDLE { bless [] }}
        {package Bar; sub error { "BAR" }}
        tie *CALC, "Foo"; bless *CALC{IO}, "Bar";
        print CALC->error'
    BAR

<snip>
> Now isn't that curious?  
> 
> First, there is indeed a *CALC{IO} "in scope", as the output of line 6,
> "IO::Handle=IO(0x3c041230)", duly reports.  I think part of your problem 
> might be that you're talking about "scope" and globals at the 
> same time.  

Yes, 'scope' is the wrong word. What I meant was, 'if, at the point
where the method is called, there is a filehandle *CALC{IO} and there is
not a package CALC::, the method will be called on the filehandle object
instead of the package'. I was being (perhaps too) brief.

> Second, lines 9 .. 11 show that my description of what the compiler
> generates was accurate, despite per your description there having been
> no CALC:: package or &CALC sub.  It does just what I said it did:
> 
>     ((rand(10000000000) < 1) and print('CALC'->error));

It does, yes; and -MO=Concise shows something along the lines of

    2     <;> nextstate(main 1 -e:1) v ->3
    6     <1> entersub[t1] vKS/TARG ->7
    3        <0> pushmark s ->4
    4        <$> const(PV "CALC") sM/BARE ->5
    5        <$> method_named(PVIV "error") ->6

which certainly *looks* like a method call on the package CALC. However,
if you look at what pp_method_named actually does, you will find (this
is from S_method_common in 5.10.0)

    /* sv at this point is the invocant of the method */

    if (SvROK(sv))

        /* it's a reference: deref to get the object */

    else {
	GV* iogv;

	/* this isn't a reference */
        if(SvOK(sv) && (packname = SvPV_const(sv, packlen))) {
          const HE* const he = hv_fetch_ent(PL_stashcache, sv, 0, 0);
          if (he) { 

              /* it's the name of an existing package: get the package
               * stash to use as invocant */

          }
        }

	if (!SvOK(sv) ||
	    !(packname) ||
	    !(iogv = gv_fetchsv(sv, 0, SVt_PVIO)) ||
	    !(ob=(SV*)GvIO(iogv)))
	{

            /* it's nothing we can work with: report an error */

	}

	/* it _is_ a filehandle name -- replace with a reference */
	*(PL_stack_base + TOPMARK + 1) = sv_2mortal(newRV((SV*)iogv));
    }

That last case is important: it means that either a glob or string
containing the name of an existing global filehandle can be used to
invoke methods on the filehandle object, *provided* there isn't an
existing package by that name.

> Even if you did dig down into *CALC, pray tell which of the two distinct
> fd's (and ensuant PerlIO or stdio streams) it represents do you think
> it is supposed to report ->error() on, anyway, and how is it to know that?

If you simply tie a filehandle, and then call ->error on it, it will
call IO::Handle->error; which, if course, will report '-1' since
*CALC{IO} isn't actually an open filehandle. What I was suggesting is
that someone could choose to rebless *CALC{IO} into a different class,
which could then provide a sensible implementation of ->error that
corresponded to whatever the filehandle was tied to.

For instance, in the case of your double filehandle, you could write
Tie::Open2::Handle which reported an error if either stream had the
error flag set. You would need to rebless the IO slot after performing
the tie, of course, but since we were talking about a situation where
the tie was done implicitly anyway that doesn't matter.

<snip>
> Also, things work quite the way you say they do, as I shall
> proceed to demonstrate.
> 
> This works:
> 
>     % perl -MIO::Handle -E 'say STDOUT STDOUT->error() ? 1 : 0'
>     0
> 
>     % unperl -MIO::Handle -E 'say STDOUT STDOUT->error() ? 1 : 0'
>     use IO::Handle;
>     BEGIN {
> 	$^H{'feature_say'} = q(1);
> 	$^H{'feature_state'} = q(1);
> 	$^H{'feature_switch'} = q(1);
>     }
>     #line 1 "-e"
>     say(STDOUT ('STDOUT'->error ? 1 : 0));
>     -e syntax OK
> 
> But this does not:
> 
>     % perl -MIO::Handle -E '*CALC = *STDOUT; say CALC CALC->error() ? 1 : 0'
>     Can't locate object method "CALC" via package "IO::Handle" at -e line 1.
>     Exit 19
> 
>     % unperl -MIO::Handle -E '*CALC = *STDOUT; say CALC CALC->error() ? 1 : 0'
>     use IO::Handle;
>     BEGIN {
> 	$^H{'feature_say'} = q(1);
> 	$^H{'feature_state'} = q(1);
> 	$^H{'feature_switch'} = q(1);
>     }
>     #line 1 "-e"
>     (*CALC = *STDOUT);
>     #line 1 "-e"
>     say(('CALC'->CALC->error ? 1 : 0));
>     -e syntax OK

That's just the flaky 'indirect object syntax' (I know you don't like
that term; 'dative method syntax', if you like) parsing getting in the
way. You can make it behave without telling Perl that CALC is a
filehandle:

    $ perl -MO=Deparse -e'print CALC "CALC"->error'
    print CALC 'CALC'->error;
    -e syntax OK

and then the method call works just fine

    $ perl -MIO::Handle -le'*CALC = *STDOUT; print CALC "CALC"->error'
    0

The what-should-I-make-of-this-bareword logic, which happens at compile
time, is quite separate from the is-this-a-filehandle-or-a-package
logic, which happens at runtime.

> The compiler generates different code, but it's hardly a matter of whether
> some global entity should be "in scope".  "Visible at compilation time"
> might work better, but I'm not going to do your work for you.

'Visible at method-call time' is exactly what I meant. I should have
been clearer.

> Only barewords have this compilation gotcha.  *Indirect* handles used 
> in the dative slot do *not* have this issue, and they are welcome to 
> be globals, not even scoped variables.
> 
>     % perl -MIO::Handle -E '$tmp = *STDOUT{IO}; say $tmp $tmp->error() ? 1 : 0'
>     0

Only the first needs to be a variable to make the parser behave. Perl
seems to be assuming no-one would ever want to make a method call like

    $meth Class 1, 2, 3;

with a variable method name:

    $ perl -MIO::Handle -le'
        $tmp = *STDOUT{IO}; *CALC = *STDOUT;
        print $tmp CALC->error;'
    0

or you can use a block:

    $ perl -MIO::Handle -le'*CALC = *STDOUT; print { CALC } CALC->error'
    0

(that relies on no strict 'subs', of course). Anything that stops perl
from trying to make the first CALC a method name to be invoked on the
second.

> Yes, I'm testy.  You'd be, too, if someone mis/hypercorrected you
> without ever even checking their results first!  
> 
> Don't you think?

Again: I apologise for my earlier bluntness.

Ben

-- 
"If a book is worth reading when you are six,                * ben@morrow.me.uk
it is worth reading when you are sixty."  [C.S.Lewis]



nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About