Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
PerlMonks  

these aren't locals, but they seem to act like it

by argv (Pilgrim)
on Mar 11, 2007 at 19:08 UTC ( [id://604242]=perlquestion: print w/replies, xml ) Need Help??

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

I have some functions that use far too many parameters, and many of the parameters are optional. So as to simplify, I'm converting a lot of them from using many params, so just using one: a hash (whose key/value pairs represent the params that used to be passed).

So, now what I have is a nice elegant API that looks like:

foo ( { param => "value", ... } );

Until I actually convert all the CALLS to the function in the new format, I've written the functions in such a way that it can accept either form by using ref() on the first param and seeing if it's a hash. If so, the function's vars are set to the has key/value pairs. otherwise, the rest of the args are read from @_.

All's well and good, and things work properly. However, for one function, there were so many params that I thought I would simplify the above process using a loop as illustrated in the code below. The surprise isn't so much to do with the parameters to the function, but the setting of the values to the local my variables within the function.

Can someone explain the scoping going on here that isn't preserving the variables' values outside of the "using args..." block?

#!/usr/bin/perl use strict; sub foo { my ( $flags, $img, $name, $big, $gallery, $page, $caption, $num ); my $opts = shift; if (ref $opts eq "HASH") { print "using hash...\n"; no strict; for (qw( flags img name big gallery page caption num ) ) { $$_ = $opts->{$_}; print "\tsetting $_ to $$_ ($opts->{$_})\n"; } use strict; } else { print "using args...\n"; $flags = $opts; # these vars are local for some reason... ??? ( $img, $name, $big, $gallery, $page, $caption, $num ) = @_; print "flags = $flags, img = $img\n"; # they're getting set... # but their values are now going to vanish... } print "reviewing what's been set...\n"; no strict; for (qw( flags img name big gallery page caption num ) ) { print "\t$_ = $$_\n"; } use strict; } # it doesn't matter which order you call these functions... foo( "hi", "apoinv", "slkjhfp", "876", "poi", "asdf", "sdf", "there" ) +; foo( { flags => "hi", img => "apoinv", name => "slkjhfp", big => "876", gallery => "poi", page => "asdf", caption => "sdf", num => "there" });

Replies are listed 'Best First'.
Re: these aren't locals, but they seem to act like it
by jdporter (Paladin) on Mar 11, 2007 at 19:30 UTC

    The no strict should be the giveaway. When you do

    $name = 'flags'; print $$name;
    you are accessing a global (i.e. package) variable named $flags, not a lexical variable. If you print out all the lexical variables explicitly, instead of trying to loop over them by name, you'll see that they are set as you expected.

    A word spoken in Mind will reach its own level, in the objective world, by its own weight
      you are accessing a global (i.e. package) variable named $flags, not a lexical variable.

      I see... I was under the mistaken idea that perl would dereference $$var up the scoping levels till it found a match, and only use global/package names as last resort.

      It'd be sure nice to have such a feature. :-/

        It'd be sure nice to have such a feature. :-/
        Well, I think you can, because eval STRING behaves pretty much this way. So, you can use
        $ref = eval "\\\$$name";
        to get a real reference of the variable you know the name of — a reference, not the value, so you're actually able to change the value. Try it:
        #! perl -w use strict; my $ref; my $name = 'x'; our $x; $x = 'global'; $ref = eval "\\\$$name"; print "\$$name = '$$ref'\n"; { my $x = 'lexical'; $ref = eval "\\\$$name"; print "\$$name = '$$ref'\n"; { my $x = 'nested lexical'; $ref = eval "\\\$$name"; print "\$$name = '$$ref'\n"; } }
        Result:
        $x = 'global' $x = 'lexical' $x = 'nested lexical'

        Caution: You do realize that you're skating on very thin ice, do you? In other words, only use this feature to help you debug what is going on (even though I'm not sure how it could help), do not ever use this in production code to reach for any variable that can be used anywhere in your code.

        But, for debugging purposes, it still won't work to get at variable you don't have normal access to, such as lexicals in a sub that calls your sub. For that purpose, there is PadWalker.

        That would make code difficult to debug (what if you have variable shadowing?), but if you really want some deep hurting, try PadWalker.

        Why it's stupid to `use a variable as a variable name' Part 1 2 3
      More succinctly: symbolic refs affect symbol table variables, not lexicals.
Re: these aren't locals, but they seem to act like it
by GrandFather (Saint) on Mar 11, 2007 at 20:38 UTC

    You might like to consider accessing the parameters using the hash. When you start using Perl's OO features you will find that that is standard technique anyway. Consider:

    use strict; use warnings; foo( "hi", "apoinv", "slkjhfp", "876", "poi", "asdf", "sdf", "there" ) +; print "\n"; foo( { flags => "hi", img => "apoinv", name => "slkjhfp", big => "876", gallery => "poi", page => "asdf", caption => "sdf", num => "there" }); sub foo { my %args; if (ref $_[0] eq "HASH") { %args = %{$_[0]}; } else { @args{qw(flags img name big gallery page caption num)} = @_; } print "$_:\t$args{$_}\n" for sort keys %args; }

    Prints:

    big: 876 caption: sdf flags: hi gallery: poi img: apoinv name: slkjhfp num: there page: asdf big: 876 caption: sdf flags: hi gallery: poi img: apoinv name: slkjhfp num: there page: asdf

    DWIM is Perl's answer to Gödel

      I love your solution:

      my %args; if (ref $_[0] eq "HASH") { %args = %{$_[0]}; } else { @args{qw(flags img name big gallery page caption num)} = @_; }

      But one of the things I left out so as to keep the code concise was the use of default values to apply to the locals in the even the caller didn't pass them in. This is why I used the looping mechanism that I did. Rather than specifying each line precisely, which worked, I wrote a loop for brevity and attempted elegance. The solution above is what the doctor (or grandfather) ordered. So, let's revisit my other original objective again and replace what i took out:

      $defaults = { flags => 0x8001, img => "/path/file.jpg", ... }; for (qw( flags img name big gallery page caption num ) ) { $$_ = $opts->{$_} || $defaults->{$_}; }

      We know why this doesn't work now, but how can I apply defaults from a default hash for those values not passed in using the new method (to me) shown above?

        consider:
        my %args = ( flags => 0x8001, img => "/path/file.jpg", ... ); if (ref $_[0] eq 'HASH') { @args{keys %{ $_[0] }} = values %{ $_[0] }; } else { for (qw( flags img name big gallery page caption num )) { $args{$_} = shift if @_; } }
        If you want the defaults to apply whether the parameters are passed by name or not, after you have initialized parameters from the values passed in to your sub you could simply say something like this (untested):
        foreach my $k (keys %args) { if (!defined($args{$k})) { $args{$k} = $defaults->{$k}; } }
Re: these aren't locals, but they seem to act like it
by sgifford (Prior) on Mar 12, 2007 at 05:34 UTC
    Since jdporter seems to have identified the problem, here's a fairly clean way to do what you want with a minimum of typing:
    ($flags, $img, $name, $big, $gallery, $page, $caption. $num ) = @$opts{qw(flags img name big gallery page caption num)};

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://604242]
Approved by Joost
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others goofing around in the Monastery: (2)
As of 2024-04-25 21:24 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found