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

In the Shiny Ball Book (Effective Perl Programming) under "Item 26: Pass references instead of copies" there is the "Using local * on reference arguments" subitem (pages 100-101). An example is given:
sub max_v_local { local (*a, *b) = @_; my $n = @a > @b ? @a : @b; my @result; for( my $i = 0; $i < $n; $i++ ) { push @result, $a[$i] > $b[$i] ? $a[$i] : $b[$i]; } @result; }
Presumably one would call this as follows:
my @answer = max_v_local( \@mylist, \@yourlist );
Can I use this typeglob trick with 'use strict'? If so then read on...

I thought this would be great to use in other circumstances but I keep running into problems. Here's some example code (The typeglob is near the middle in the foreach block; This snippet reads in a CSV file and generates an XML file; @c describes the CSV file; For the curious, grbr is an internal identifier).

sub test_xml_writer { my( @array, $aref ); my $io = new IO::File '<test.txt' or die( "IO Failure" ); my $csv = new Text::CSV_XS; while( 1 ) { $aref = $csv->getline( $io ); $#{ $aref } > -1 ? push @array, $aref : last; } $io->close(); $io = new IO::File '>test-out.xml' or die "Could not open output f +ile"; my $w = new XML::Writer( OUTPUT => $io, DATA_MODE => 1, DATA_INDEN +T => 2 ); $w->xmlDecl( undef , 1 ); $w->startTag( 'assets' ); foreach ( @array ) { $w->startTag( 'machine' ); my @c = qw( name ip_address snmp_community grbr model_serial ) +; local *a = $_; # my @a = @{$_}; $w->dataElement( $c[0], $a[0], src => 'netview' ) if defined $ +a[0]; $w->dataElement( $c[1], $a[1] ) if defined $a[1]; $w->dataElement( $c[2], $a[2], type => 'rw' ) if defined $a[2] +; $w->dataElement( $c[3], $a[3], src => 'data' ) if defined $a[3 +]; $w->dataElement( $c[4], $a[4], src => 'data' ) if defined $a[4 +]; $w->endTag(); } $w->endTag(); $w->end(); $io->close(); }
Which gives me the following errors: Note that I have use strict and use warnings and that the code above starts on line 92:
Variable "@a" is not imported at ./test.pl line 112. Variable "@a" is not imported at ./test.pl line 112. Variable "@a" is not imported at ./test.pl line 113. Variable "@a" is not imported at ./test.pl line 113. Variable "@a" is not imported at ./test.pl line 114. Variable "@a" is not imported at ./test.pl line 114. Variable "@a" is not imported at ./test.pl line 115. Variable "@a" is not imported at ./test.pl line 115. Variable "@a" is not imported at ./test.pl line 116. Variable "@a" is not imported at ./test.pl line 116. Global symbol "@a" requires explicit package name at ./test.pl line 11 +2. Global symbol "@a" requires explicit package name at ./test.pl line 11 +2. Global symbol "@a" requires explicit package name at ./test.pl line 11 +3. Global symbol "@a" requires explicit package name at ./test.pl line 11 +3. Global symbol "@a" requires explicit package name at ./test.pl line 11 +4. Global symbol "@a" requires explicit package name at ./test.pl line 11 +4. Global symbol "@a" requires explicit package name at ./test.pl line 11 +5. Global symbol "@a" requires explicit package name at ./test.pl line 11 +5. Global symbol "@a" requires explicit package name at ./test.pl line 11 +6. Global symbol "@a" requires explicit package name at ./test.pl line 11 +6. Execution of ./test.pl aborted due to compilation errors.

What am I doing wrong? -- Argel

Replies are listed 'Best First'.
Re: Confused about typeglobs and references
by Ovid (Cardinal) on Nov 04, 2002 at 20:51 UTC

    Your questions been answered, but here's a bit more background information as to what's happening.

    A typeglob is merely a value in a symbol table that points to package variables of the same name. For each typeglob, there are 6 slots (did I miss one? I keep thinking I missed one). For example, in package %SomePackage::, if we refer to *SomeVar, we're referring to a typeglob with the following slots:

    
      Symbol Table    Typeglob    Values
                                          
     %SomePackage::   *SomeVar    $SomeVar  (scalar)
                                  @SomeVar  (array)
                                  %SomeVar  (hash)
                                  SomeVar   (format)
                                  SomeVar   (file handle)
                                  &SomeVar  (code)

    In the above little diagram, you can refer to the scalar as $SomePackage::SomeVar.

    For Perl to access a package variable, it must look up the typeglob in the appropriate symbol table and then check to see if the appropriate slot for the appropriate variable type has a value.

    The local keyword merely takes the value of any package variable (which, of course, is a slot in a typeglob), saves the value, let's you reassign a value to that package variable and then restores the saved value when the scope exits. What this means is that you cannot use local on a lexically scoped variable (unless you predeclare it with our, but that's a lexically scoped package variable and will just make your head hurt). In other words, if you declare a variable with my, you don't use local on it. However, even with package variables you rarely want to use local unless you are either:

    • Using a built in pseudo-global such as $_ and you don't want to accidentally interfere with the rest of the program.
    • You are using a format or filehandle (both of which irritatingly lack a sigil) which is always a package variable, but you still don't want to risk affecting other parts of the program.

    Note: like using strict, there are times to break these rules, but don't do that unless you can clearly explain why you shouldn't break them :)

    Which brings us back to your original question of whether or not you can use that typeglob trick with strict. If you read further in the "Shiny Ball" book, you'll see that passing typeglobs in the manner described was used in older versions of Perl where references didn't exist. Now we have references and unless you're a Damian Conway or a related construct, I would not use this syntax. Use explicit references instead.

    Cheers,
    Ovid

    Join the Perlmonks Setiathome Group.

    New address of my CGI Course.

Re: Confused about typeglobs and references
by chromatic (Archbishop) on Nov 04, 2002 at 20:16 UTC

    Assigning a reference to a typeglob (not a reference to a typeglob!) populates the appropriate slot within the typeglob. For example, *a = []; populates something you can address as @a. If you're passing arguments to a function, what would happen if someone sent the wrong reference type? *a = {}; is very different.

    Consequently, I think you're much better off working with references explicitly. Run-time symbol table manipulation is powerful, and one percent of the time it's absolutely necessary. I think this falls strongly within the remaining 99%.

      Thanks for all the comments!

      Chromatic, you have me wondering if I should start using either prototypes or 'ref' to verify what reference I have. Otherwise, there seems to be little difference between the typeglob trick and using references -- if you assume you have an array you're still not going to catch the hash problem until runtime (perhaps you will get a more descriptive error message).

      Do many of you check the reference type? And is there a way to figure out what type of variable a typeglob is aliasing to?

        Prototypes won't really help here. All they do is coerce the passed arguments into what you're expecting. You still have to document the API carefully so people are enlightened as much as possible. (I almost never use them.)

        My point is that with using references directly, you'll get a better error message. If you're expecting an array reference and someone passes a hash reference, Perl will complain that you're doing arrayish operations on something that doesn't look like a hash. (If you're lucky, you won't get one of the obscure pseudohash errors...)

        I'm not sure what you mean by figure out what type of variable a typeglob is aliasing to. You can use defined and exists on the typeglob slot to see if something's there and usable, but that has other subtleties and optimizations.

        Checking the reference type with ref or UNIVERSAL::isa() is much better. It's much simpler.

        Don't use prototypes unless you can clearly explain why you're using them. Their only significant benefit in Perl is to change how you call a subroutine, but they are so filled with caveats and gotchas that they're easy to use incorrectly. Read this recent thread on prototypes for more info, particularly the short and accurate explanation by Dominus.

        As for using ref: I hate to say it, but I'd avoid that also. Take a look at code on this site that uses ref and you'll find that most (not all!) instances of it are indicative of some design flaw in the code.

        Cheers,
        Ovid

        Join the Perlmonks Setiathome Group.

        New address of my CGI Course.

Re: Confused about typeglobs and references
by cLive ;-) (Prior) on Nov 04, 2002 at 19:42 UTC
    If you're using local, I'm pretty sure the var needs to be previously declared as global (ie, the error is that the script can't localise something that doesn't exist). I don't use local much though...

    Put either of these at the beginning of the script:

    our @a = (); # or use vars qw(@a);

    That should do the trick - unless I'm missing something else from your snipped code...

    .02

    cLive ;-)

      Right you are cLive! Thanks! -- Argel
Re: Confused about typeglobs and references
by Zaxo (Archbishop) on Nov 04, 2002 at 20:02 UTC

    You're doing something right, using strict to catch unintended global variables. You can say:

    use vars qw( @a @b ); # or else for recent perl # our (@a, @b);

    Beware *a and *b, you're treading near $a and $b, which are sacred to sort. Expect bizarre bugs in code that uses those names. Your example function is probably ok since it doesnt call sort and localizes the typeglob, but imagine what could happen if some function that sorts were called, and a user fed the function a pair of refs to scalars. It would compile, appear to work, and might even return correct values - for most inputs.

    After Compline,
    Zaxo

      Beware *a and *b, you're treading near $a and $b, which are sacred to sort.

      I think that's a little misleading. $a and $b are used by sort, but otherwise are not special to sort.

      Here's the deal:

      $a and $b are pre-declared package variables.
      That means you can use them -- without declaring them first -- even under strict.

      Of course, as with all global variables, safe programming means localizing.

      So if there's any chance that you've been called from within a sort (unlikely), localize $a and $b before touching them. And if there's any chance that you will call sort while working with $a or $b, be sure to localize them before passing control to where the sort might happen.

      Beware *a and *b, you're treading near $a and $b, which are sacred to sort. Expect bizarre bugs in code that uses those names.
      Wow! I guess somne of the examples in Effective Perl Programming might not be so effective after all! Thanks for the tip! -- Argel
Re: Confused about typeglobs and references
by jdporter (Paladin) on Nov 04, 2002 at 22:34 UTC
    I wish to point out that Item 26 is really talking about two different things, and smushing them together is obscuring the important points.

    First, Joe is talking about passing arguments to subroutines efficiently. That's the reason for passing them by reference. You pass in a reference, the subroutine gets a reference.

    Secondly, there is the issue of accessing an array (or whatever) efficiently/conveniently, when you have a reference to it. The idea is that if you have an array-ref $a_ref, you would like to be able to access its contents as if it were not a reference but a regular array variable (e.g. @a).

    The latter is achieved by assigning the reference to a typeglob.

    Given

    $a_ref = [ ... ]; # you can: print $a_ref->[0];
    But you can access the contents of the anonymous array by giving it a name, i.e by associating it with an entry in the symbol table. That's what typeglobs are good for.
    *a = $a_ref; # now you can: print $a[0];
    By talking about this technique in conjunction with argument passing, it kind of obscures the essence of what's going on here.

    PS - strict does not care if you use typeglobs. That is, saying

    use strict; *foo = [];
    will not raise an exception.
    It's only when you go to use @foo (or other "normal" variable named foo) that strict will object, if the variable hasn't been declared.