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

It seems that there should be a way to implement the following that only specifies the name list once instead of twice, but I can't figure out how to do it:
package MYPACKAGE; [...] my ($ab, $cd, $ef, ..., $yz) = map { ... some function of $_ ... } ('ab', 'cd', ..., 'yz');
An example might be computing hash keys for field names:
my ($field1, ...) = map { __PACKAGE__ . '::' . $_ } ('field1', ...); [...] ... $self->{$field1} ...
How can this be done as...
foreach (qw(ab cd ef ... yz)) { ??? };
...preferably under "use strict;"?

Thanks,
A.C.

Replies are listed 'Best First'.
Re: Computed "my" declarations?
by Tanktalus (Canon) on Oct 26, 2005 at 20:42 UTC

    Use a hash instead.

    my %data = map { $_ => do { ... some function of $_ ... } } ('ab', 'cd', ..., 'yz');

Re: Computed "my" declarations?
by diotalevi (Canon) on Oct 26, 2005 at 22:28 UTC

    You'll have to use code generation. The result is that you get a subroutine that's hard-coded to use a list of stuff and you just change how it compiles if you need to alter it. I included a #line declaration so the compilation errors, if any, would come out on the right line.

    package Foo::Bar; BEGIN { my @attributes = qw( ... ); eval '#line ' . (1+__LINE__) . ' "' . __FILE __ . '" sub ... { ... my ( ' . join( ', ', map { "\$$_" } @attributes ) . ' ) = @{$self}{qw(' . join( ' ', @attributes ) . ')}; ... }'; die $@ if $@; }

    This node has been updated to fix some minor bugs including the omission of the elipses after the my declaration. This mistake on my part confused someone about my intentions. I did not intend to indicate that the subroutine had no more statements in it.

      Won't that create the my variables inside the scope of your sub, which will then go out of scope pretty much immediately? I don't think the OP is asking the right question because s/he's talking about package-scope variables, which are, by their nature, global, not lexical. In that case, it becomes much easier:

      for (@attributes) { no strict 'refs'; ${$package . '::' . $_} = do { ... something to do with $_ ... }; }
      Of course, if $package is supposed to be this package, then that is a bit simpler - change $package to __PACKAGE__, and you're done.

      Update: now that diotalevi has added some extra ellipses, I can see how he's proposing to do this without the variables going out of scope. And now I think I better understand the OP's question, which has nothing to do with globals because he's trying to create a variable that is a string. And that string just happens to look like a global variable name.

      In this case, I'd suggest a number of possibilities. First thing to come to my head is stupid, so I'm going to skip that one. ;-) The next idea is to just create the constants that you're going to need using constant:

      use constant { map { uc $_ => __PACKAGE__ . "::$_" } qw(name timestamp etc.) };
      Then you don't need to keep redeclaring anything.

      (I said a number of possibilities, right?) Still, use a hash. The usage would be something like $self->{$my{name}} which is still a lot lighter than $self->{__PACKAGE . '::name'}

      And back to the stupid one. Create a package, say called "My" or "P" or something. Set up an AUTOLOAD function which looks at the package of the caller, and just returns that package name concatenated with '::' and then the function being called. Usage would then be $self->{P->name}

      But don't do that last one.

        No. It is creating a subroutine which lists everything explicitly when compiled but didn't have to be written that way. Assuming the named variables were foo, bar, and baz, here is what the subroutine would look like. I've left all the uninteresting parts filled with ellipses and you're expected to understand that something useful would go there.

        Obviously, the code author is going to want to do something with those variables or there's no point to extracting them in the first place.

        sub ... { ... my ( $foo, $bar, $baz ) = @{$self}{qw(foo bar baz)}; ... }

        I think it is an error on everyone's part to talk about packaged scoped variables. That's only because there's no way to refer to a lexical symbolically and its a bug in everyone else's brain that they went that way.

        PS, I updated this node and the parent. Both to add this note and to fix a bug where the list of attributes wasn't correctly quoted in qw(...) marks.

Re: Computed "my" declarations?
by BUU (Prior) on Oct 26, 2005 at 21:33 UTC
    Maybe it's just me, but I can't understand what the hell you're trying to accomplish. Why do you declare a set of lexical variables, then declare another list containing the names of the lexicals? Why does your example involve package variables with the same names as lexicals? What are you trying to accomplish?
      I don't want to use a hash key like 'name' because other classes in the hierarchy may already be using that. So I will use __PACKAGE__ . '::name' instead to avoid the conflict. But I would rather not have to write that every time I reference the value of the field. So I am trying to declare $name with value __PACKAGE__ . '::name', $timestamp with value __PACKAGE__ . '::timestamp', etc. so that I can refer to my package's fields with the simpler $self->{$name}.

      The question was how to do that without having to repeat the list of field names with my ($name, $timestamp, ...) = map {...} ('name', 'timestamp', ...);

      Thanks,
      A.C., oops, pm not /. so I guess that's A.M.

        Better than a timestamp, store another hash in a key named after your package. In your Foo->new method, store a hash ref.

        sub foo { my $self = shift; my $data = $self->{+__PACKAGE__}; $data->{...} }
             I don't want to use a hash key like 'name' because other
             classes in the hierarchy may already be using that.
        
        You should strongly consider converting over to using inside-out objects which avoids this hassle altogether. Start by looking at Class::Std.

        Remember: There's always one more bug.