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)

Thread Next
From:
Tom Christiansen
Date:
August 2, 2008 18:31
Subject:
Re: Near-FMTEYEWTK instructorial on ties, handles, and methods (was: How to tell whether readline got an error or EOF)
Message ID:
14991.1217727075@chthon
> 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.  

It's--hm, precipitous?--at best; I leave "the worst" for you to fill in on
your own, based on your cultural sensibilities about contradicting.

I say this because *I* myself very *specifically* checked this very thing
out thoroughly before posting what I did.  So *I* know what it compiles
into; while you, rather self-evidently, do not.  Perhaps you just weren't
paying close enough attention the first time around.

So watch.

This program, o2d2: 

    #!/usr/bin/env perl5.10.0
    use 5.010_000;
    use Tie::Open2;
    tie(*CALC, "Tie::Open2", "dc")
	|| die qq(can't tie to "dc" command: $!);
    say *CALC{IO};
    my $sum = 2;
    print CALC "$sum\n";
    print CALC->error() if rand(1e10) < 1;
    CALC->error() if rand(1e10) < 1;
    error CALC (1..5) if rand(1e10) < 1;
    for (1 .. 7) {
	print CALC "$sum * p\n";
	chomp($sum = <CALC>);
	print "$_: $sum\n";
    }
    close(CALC)   || warn "can't close CALC: $!";
    close(STDOUT) || die  "can't close STDOUT: $!";
    __END__
    ## Expected output:
    IO::Handle=IO(0x3c041230)
    1: 4
    2: 16
    3: 256
    4: 65536
    5: 4294967296
    6: 18446744073709551616
    7: 340282366920938463463374607431768211456
    will, when uncompiled by running through 

    alias unperl 'perl -MO=Deparse,-p,-q,-x=10,-l'

produce the following, showing that I was and am correct:

    #line 0 "o2d2"
    sub BEGIN {
    #line 2 "o2d2"
	require(5.01);
    #line 2 "o2d2"
    #line 2 "o2d2"
	;
    }
    use Tie::Open2;
    BEGIN {
	$^H{'feature_say'} = q(1);
	$^H{'feature_state'} = q(1);
	$^H{'feature_switch'} = q(1);
    }
    #line 4 "o2d2"
    (tie(*CALC, 'Tie::Open2', 'dc') or die((q[can't tie to "dc" command: ] . $!)));
    #line 6 "o2d2"
    say(*CALC{'IO'});
    #line 7 "o2d2"
    (my $sum = 2);
    #line 8 "o2d2"
    print(CALC ($sum . "\n"));
    #line 9 "o2d2"
    ((rand(10000000000) < 1) and print('CALC'->error));
    #line 10 "o2d2"
    ((rand(10000000000) < 1) and 'CALC'->error);
    #line 11 "o2d2"
    ((rand(10000000000) < 1) and 'CALC'->error((1..5)));
    #line 12 "o2d2"
    foreach $_ (1 .. 7) {
    #line 13 "o2d2"
	print(CALC ($sum . " * p\n"));
    #line 14 "o2d2"
	chomp(($sum = <CALC>));
    #line 15 "o2d2"
	print(((($_ . ': ') . $sum) . "\n"));
    }
    #line 17 "o2d2"
    (close(CALC) or warn((q[can't close CALC: ] . $!)));
    #line 18 "o2d2"
    (close(STDOUT) or die((q[can't close STDOUT: ] . $!)));
    __DATA__
    ## Expected output:
    IO::Handle=IO(0x3c041230)
    1: 4
    2: 16
    3: 256
    4: 65536
    5: 4294967296
    6: 18446744073709551616
    7: 340282366920938463463374607431768211456

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.  

What does it mean to be for a global in scope?   

Nothing.  Or next to nothing.  A global is nearly by very definition
accessible from all scopes.  And *CALC{IO} is a global, because *CALC is a
global.  You can't even play temporizing local $hash{ITEM} games with it
either, as you'd soon find out if you tried.  So sure, it *shall* be "in
scope", but I think that means nothing.

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));
    ((rand(10000000000) < 1) and 'CALC'->error);
    ((rand(10000000000) < 1) and 'CALC'->error((1..5)));

After you look at the runtime output of line 6, and the uncompiled
output of lines 9 .. 11 above, which indeed compiled into what I said
they did, but are not about to get you to IO::Handle::error, I can't see
your having any choice but to reconsider what you said when I stated
quite clearly that:

    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.

(Yes, that was repeated for the logic-impaired.)

> Otherwise STDIN->error() wouldn't work either.

A false premise can lead anywhere.  Your premise being false,
says little for your "otherwise".

Compare:

    % perl -E 'say STDIN->error ? 1 : 0'
    Can't locate object method "error" via package "IO::Handle" at -e line 1.

    % unperl -E 'say STDIN->error ? 1 : 0'
    BEGIN {
	$^H{'feature_say'} = q(1);
	$^H{'feature_state'} = q(1);
	$^H{'feature_switch'} = q(1);
    }
    #line 1 "-e"
    say(('STDIN'->error ? 1 : 0));
    -e syntax OK

with 

    % perl -MIO::Handle -E 'say STDIN->error ? 1 : 0'
    0

    % unperl -MIO::Handle -E 'say STDIN->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(('STDIN'->error ? 1 : 0));
    -e syntax OK

or even 

    % perl -MIO::Handle -E 'say STDOUT error STDOUT () ? 1 : 0'
    0

    % unperl -MIO::Handle -E 'say STDOUT error STDOUT () ? 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

Please enjoy noting the difference between the two ways the STDOUT token
uncompiles there in the last line where it appears twice.

I think you have some explaining to do.  Start by redacting your
initial, blunt, and errant contradiction:

> No, it doesn't; 

and take it from there.  Oh, and I really do suggest testing your code.  I
did; you didn't; and that's why you erred.

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?

The tie is clouding your vision.

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

Nor even this:

    % perl -MIO::Handle -E 'INIT{*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 'INIT{*CALC = *STDOUT}; say CALC CALC->error() ? 1 : 0'
    use IO::Handle;
    #line 1 "-e"
    sub INIT {
	BEGIN {
	    $^H{'feature_say'} = q(1);
	    $^H{'feature_state'} = q(1);
	    $^H{'feature_switch'} = q(1);
	}
    #line 1 "-e"
	(*CALC = *STDOUT);
    }
    BEGIN {
	$^H{'feature_say'} = q(1);
	$^H{'feature_state'} = q(1);
	$^H{'feature_switch'} = q(1);
    }
    #line 1 "-e"
    say(('CALC'->CALC->error ? 1 : 0));
    -e syntax OK

Because you need this:

    % perl -MIO::Handle -E 'BEGIN{*CALC = *STDOUT}; say CALC CALC->error() ? 1 : 0'
    0

    % unperl -MIO::Handle -E 'BEGIN{*CALC = *STDOUT}; say CALC CALC->error() ? 1 : 0'
    use IO::Handle;

    #line 0 "-e"
    sub BEGIN {
	BEGIN {
	    $^H{'feature_say'} = q(1);
	    $^H{'feature_state'} = q(1);
	    $^H{'feature_switch'} = q(1);
	}
    #line 1 "-e"
	(*CALC = *STDOUT);
    }
    BEGIN {
	$^H{'feature_say'} = q(1);
	$^H{'feature_state'} = q(1);
	$^H{'feature_switch'} = q(1);
    }
    #line 1 "-e"
    say(CALC ('CALC'->error ? 1 : 0));
    -e syntax OK

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.

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

    % unperl -MIO::Handle -E '$tmp = *STDOUT{IO}; say $tmp $tmp->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"
    ($tmp = *STDOUT{'IO'});
    #line 1 "-e"
    say($tmp ($tmp->error ? 1 : 0));
    -e syntax OK

Nor do they with non-dative invocation:

    % perl -MIO::Handle -E '$tmp = *STDOUT{IO}; $tmp->say($tmp->error() ? 1 : 0)'
    0

    % unperl -MIO::Handle -E '$tmp = *STDOUT{IO}; $tmp->say($tmp->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"
    ($tmp = *STDOUT{'IO'});
    #line 1 "-e"
    $tmp->say(($tmp->error ? 1 : 0));
    -e syntax OK

Or with an indirect-handle used nondatively:

    % perl -MIO::Handle -E '$H{tmp} = *STDOUT{IO}; $H{tmp}->say($H{tmp}->error() ? 1 : 0)'
    0

    % unperl -MIO::Handle -E '$H{tmp} = *STDOUT{IO}; $H{tmp}->say($H{tmp}->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"
    ($H{'tmp'} = *STDOUT{'IO'});
    #line 1 "-e"
    $H{'tmp'}->say(($H{'tmp'}->error ? 1 : 0));
    -e syntax OK

Or indirect-handle used datively, albeit hidden in a block:

    % perl -MIO::Handle -E '$H{tmp} = *STDOUT{IO}; say { $H{tmp} } $H{tmp}->error() ? 1 : 0'
    0

    % unperl -MIO::Handle -E '$H{tmp} = *STDOUT{IO}; say { $H{tmp} } $H{tmp}->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"
    ($H{'tmp'} = *STDOUT{'IO'});
    #line 1 "-e"
    say({$H{'tmp'};} ($H{'tmp'}->error ? 1 : 0));
    -e syntax OK

There; I've done most of your homework for you now.

But even with all that, you still need to explain which of my tie-twinned
filehandles for the open2 you somehow think ->error is going to be called
on, or what doing so would *mean* without a delegated method.  Notice 
very carefully that no overt @ISA inheritance was active in Tie::Open2.

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

Don't you think?

--tom

-- 
    "Those who know more than me will correct me if I'm wrong."
    "Those who know less than me will correct me if I'm right."

Thread Next


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