Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

lexical vs. local file handles

by jo37 (Pilgrim)
on Apr 18, 2020 at 18:26 UTC ( #11115734=perlmeditation: print w/replies, xml ) Need Help??

EDIT:
I use used bareword filehandles. In the course of this discussion I was convinced not to do so.

I've seen many Perl programmers advocating the usage of lexical scalars instead. Though there are good reasons for this, it has a drawback. Filehandles in Perl are special on a syntactical level. The compiler is capable of catching errors like this:

open FH, '<', 'myfile'; print FH, "something\n";

by emitting an error:

No comma allowed after filehandle at ...

Whereas strange things happen at runtime if you use:

open my $fh, '<', 'myfile'; print $fh, "something\n";
NB: Someone else pointed to this subtle difference but sadly I don't remember who and where it was. She/he should be credited here.

So I stay with barewords and try to avoid the problems caused by the usage of global variables using this idiom:

open local *FH, '<', 'myfile'; while (<FH>) { # do something } close FH;

Some features:

  • usages of equally named file handles do not affect each other in different scopes
  • the usage in a sub does no harm if the sub is in the same package and it uses the same idiom
  • the usage in a sub of a different package does not harm.
So, as long as I follow this pattern im my own package, I feel kind of safe. Side effects of localizing a glob are easily circumvented.

The question remains: Do I miss something here? Do you see any pitfalls using this approach?

Here is an example demonstrating the issues I'm aware of.

#!/usr/bin/perl use Test2::V0; package Foobar; our $foobar = "foobar\n"; our @foobar; sub use_fh { # unlocalized use of FH in separate package open FH, '<', \$foobar; @foobar = <FH>; close FH; } package main; my $foo = "foo\n"; my $bar = "bar\n"; my $baz = "baz\n"; my @baz; sub use_localized_fh { # protect caller's FH open local *FH, '<', \$baz; @baz = <FH>; close FH; } sub close_fh { # unlocalized use of FH close FH; } # open now, use later open FH, '<', \$bar; my @foo; # create new scope { # use localized FH, protecting handle opened on \$bar open local *FH, '<', \$foo; # call sub that uses localized FH use_localized_fh; # call sub in other package that uses FH Foobar::use_fh; # FH still intact @foo = <FH>; close FH; } is \@baz, [$baz], 'got $baz in sub'; is \@Foobar::foobar, [$Foobar::foobar], 'got $Foobar::foobar in foreig +n sub'; is \@foo, [$foo], 'good: got $foo'; { open local *FH, '<', \$foo; # call sub that closes FH close_fh; @foo = <FH>; close FH; } is \@foo, [], 'bad: FH was closed in sub'; # FH at this scope is still untouched my @bar = <FH>; close FH; is \@bar, [$bar], 'good: got $bar'; done_testing;

Greetings,
-jo

$gryYup$d0ylprbpriprrYpkJl2xyl~rzg??P~5lp2hyl0p$

Replies are listed 'Best First'.
Re: lexical vs. local file handles
by haukex (Bishop) on Apr 18, 2020 at 19:04 UTC
    Do I miss something here? Do you see any pitfalls using this approach?
    • When you localize a glob, you're localizing every variable with that name - scalars, arrays, hashes, etc.
    • local's effects have dynamic scope, so they effect everything called in that scope, including code that might not be yours. You might step on someone else's globals, or they might step on yours. The major issue is that this is action at a distance: things may break in remote pieces of code, and it may be incredibly hard to connect the problem to the cause. This will not happen with lexicals.
    • Bareword filehandles also clash with package names (see also).
    • Update from here: open local *HMD, ">", \(my $x) or die $!; print HND "Foo"; close HND; only causes warnings, while open my $hmd, ">", \(my $x) or die $!; print $hnd "Foo"; close $hnd; is a compile-time error.
    use warnings; use strict; our $FH = "Hello, World!"; sub FH::awesome { print "<$FH>\n" } FH->awesome; open local *FH, "<", \my $x or die $!; FH::awesome; # => Use of uninitialized value $FH in concatenation (.) or string FH->awesome; # => Can't locate object method "awesome" via package "IO::File"
    Though there are good reasons for this, it has a drawback. Filehandles in Perl are special on a syntactical level. The compiler is capable of catching errors like this:

    You seem to be naming only a single (IMHO minor) advantage, compared to all the arguments against bareword filehandles. Unless there's something else I'm missing, for me, the arguments against bareword filehandles outweigh those in favor of them.

    Instead of using bareword filehandles, you could get into the practice of being explicit about your prints, as in print {$fh} "something\n"; - visually a whole lot more clear. (Update: see also Perl::Critic::Policy::InputOutput::RequireBracedFileHandleWithPrint.)

    See also "open" Best Practices.

    Update 2: Just to cross-reference this thread to another lexical vs. bareword filehandle discussion: Re^2: Summing numbers in a file

      Hi haukex

      thank you for your comments. I'm not fully convinced yet, though.

      When you localize a glob, you're localizing every variable with that name - scalars, arrays, hashes, etc.
      Sure. But I can keep a reference to those if needed. Or I can choose a different name for the file handle.
      local's effects have dynamic scope, so they effect everything called in that scope, including code that might not be yours. You might step on someone else's globals, or they might step on yours. The major issue is that this is action at a distance: things may break in remote pieces of code, and it may be incredibly hard to connect the problem to the cause. This will not happen with lexicals.
      As shown in my example, the usage in another package does no harm.
      Bareword filehandles also clash with package names (see also).
      Is this an issue occuring in real life? Probably I do not understand this in all detail yet.

      Still using barewords :-)

      Greetings,
      -jo

      $gryYup$d0ylprbpriprrYpkJl2xyl~rzg??P~5lp2hyl0p$

        It's probably possible to make localized bareword filehandles work, if you take a whole lot of care to avoid all the issues. But then again, you could be spending all that care on avoiding print $fh, "", couldn't you? (You could probably even write a Perl::Critic policy to avoid it - Perl::Critic::Policy::InputOutput::ProhibitBarewordFileHandles already exists.) I guess I should have worded my point above as a question:

        Other than catching the print FH, "" issue, what advantages to using bareword filehandles do you see over lexical filehandles?

        Edit: Added link to module.

        I'm not fully convinced yet, though.

        Here's one more: open local *HMD, ">", \(my $x) or die $!; print HND "Foo"; close HND; only causes warnings, while open my $hmd, ">", \(my $x) or die $!; print $hnd "Foo"; close $hnd; is a compile-time error. Warnings are easily missed in server logs or long command-line outputs.

        Sure. But I can keep a reference to those if needed. Or I can choose a different name for the file handle.

        Taken all together, doesn't it all seem to be getting a bit complicated? You've got quite a long list of things you need to keep track of: in the entire scope of the local (which you have to put in its own block to limit its scope), that is, the entire call stack, you have to know all of the names of the globs and packages that you're working with, and you have to be sure they don't clash with each other or anything else (your sub close_fh is just one example), plus you have to keep track of possible typos. None of these are things to worry about when you use lexical filehandles.

        Is all that really worth it to avoid the print $fh, typo? (If you're only ever going to be writing short command-line scripts that don't write much to STDOUT/ERR or call anyone else's code, maybe yes...?)

Re: lexical vs. local file handles
by Your Mother (Bishop) on Apr 18, 2020 at 23:36 UTC

    FWIW there is a school of thought that considers braces around the { $filehandle } a best practice. I use it, inconsistently though–

    print $fh, "something\n"; # …becomes… print { $fh }, "something\n"; # …which then tells you… syntax error at -e line 1, near "}," # …so you know to… print { $fh } "something\n";

      I like this and I'll try to use it.

      Greetings,
      -jo

      $gryYup$d0ylprbpriprrYpkJl2xyl~rzg??P~5lp2hyl0p$
Re: lexical vs. local file handles
by Anonymous Monk on Apr 18, 2020 at 21:37 UTC

    I've seen many Perl programmers advocating the usage of lexical scalars instead. Though there are good reasons for this, it has a drawback. Filehandles in Perl are special on a syntactical level. The compiler is capable of catching errors like this: The question remains: Do I miss something here? Do you see any pitfalls using this approach?

    Hi,

    Have you heard the expression "pennywise"?

    Favoring global filehandles in order to let perl catch the unicorn of typos, is "pennywise but pound foolish"

    Why wouldn't you favor  STDOUT->print(1) or  $fh->print(1) instead?

    GLOB(0x3f9abc) ought to be something you would easily notice as an error when deeloping your program

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://11115734]
Approved by marto
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others contemplating the Monastery: (6)
As of 2020-08-14 08:27 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Which rocket would you take to Mars?










    Results (75 votes). Check out past polls.

    Notices?