perl-diddler has asked for the wisdom of the Perl Monks concerning the following question:

I was setting up some packages that call each other in a program, and, of course, have the common need to define accessor routines. Rather than going about using some CPAN module like Package::Pkg, I thought it should be easy to do what I want for my limited needs in a few lines of code (ok, you can stop laughing now). So I ran into a few snags.

Why isn't this as simple to do as it looks like it should be?

sub Instance_Var ($) { my $package=__PACKAGE__; my $var=$_[0]; my $accessor="$package::$var"; -> eval " sub $accessor { my $t=shift; $t->{$var}=$_[0] if @_; $t->{$var}; }"; }
Instead of not doing what I want through some indirect method, perl refuses to even compile the program, saying:
Global symbol "$t" requires explicit package name at line indicated by '->'.

Hmmmm. At first and second (third?) glance, I'm not sure why and not sure how it should be structured, other than 'not that'. Rather than trying things at random until something works (or using the, above, mentioned CPAN module or another), I'd like to understand why this wouldn't work to do what I 'think' is clear from the example (I.e. creating a named accessor that accesses a variable in an assoc array passed in as argument, and optionally set's to the value of the argument it if an additional argument is passed to it).

Thanks!

Replies are listed 'Best First'.
Re: How do I get to this 'simple' helper?
by Corion (Patriarch) on Sep 03, 2010 at 06:02 UTC

    Here's how to do it without any eval at all. As a plus, you get compile-time syntax checking:

    sub Instance_Var ($) { my $package=__PACKAGE__; my ($var)=@_; my $accessor_name = "$package\::$var"; my $acc = sub { my $t = shift; $t->{$var}=$_[0] if @_; $t->{$var}; }; # Now install our anonymous subroutine as $package::$var no strict 'refs'; *{ $accessor_name } = $acc; }

    You might also want to see Class::Accessor and any of the other Accessor modules. Maybe also Class::Data::Inheritable.

Re: How do I get to this 'simple' helper?
by SuicideJunkie (Vicar) on Sep 02, 2010 at 21:51 UTC

    You have a string fragment of "my $t = shift;" in there. Double quotes interpolate variables like $t and $accessor. You declared my $accessor, but not $t.

    You need to escape the sigils or use single quotes to avoid interpolating the $t.

    Basically, you're trying to pass something like this to the eval:

    sub __PACKAGE__::WhatARGVsaid { my <undef>=shift; <undef>->{WhatARGVsaid}=__PACKAGE__::WhatARGVsaid if WhatA +RGVsaid; <undef>->{WhatARGVsaid}; }

      um... But but as a 'sub', this works:

      sub proto { my $p=shift; $p->{proto}=$_[0] if @_; $p->{proto}; }

      I don't see what you mean by 'my $t=shift;' being a fragment. I have 'my $p=shift;'in the code above and it works fine. Pops first arg off and uses it as the reference, tests if there's any more params, and if so, assigns the 1st one to the named value, else just returns the named value.

      oh!!!!

      DOI!

      Nevermind....

      *sigh*...this really is one of those days.

      (car sitting in drive-way won't start, tow-truck on way..., among OTHER things!)

Re: How do I get to this 'simple' helper?
by bluescreen (Friar) on Sep 02, 2010 at 23:50 UTC

    Simple fix you escape the $ for $t and $_ using \ before dollar sign

    untested
    my @vars = ('instance1','instance2',..); foreach (@vars) { eval "sub $_ { my \$self = shift; if (@_) { \$self->{$_} = shift; } return \$self->{$_}; }"; }
      if (@_) { \$self->{$_} = shift; }

      Arrays get interpolated too, so I think the code above – and likewise in OPed code – should be (note  \@_ vice  @_):

      if (\@_) { \$self->{$_} = shift; }
Re: How do I get to this 'simple' helper?
by ikegami (Patriarch) on Sep 03, 2010 at 16:30 UTC
    I find it easier to avoid interpolation in that case. You could use contatenation.
    eval(' package '.$package.'; sub '.$accessor.' { my $t=shift; $t->{'.$var.'}=$_[0] if @_; $t->{'.$var.'}; } ')

    I prefer sprintf.

    eval(sprintf( ' package %s; sub %s { my $t=shift; $t->{%s}=$_[0] if @_; $t->{%s}; } ', $package, $accessor, $var, $var ))

    Note that I actually build the sub in the right package. That changes some of the meta information associated with the sub (e.g. its __PACKAGE__).

    Update: Fixed copy and paste error.

Re: How do I get to this 'simple' helper?
by perlpie (Beadle) on Sep 03, 2010 at 03:04 UTC

    i see that your question was answered. One additional pointer: use block eval rather than string eval. Instead of wrapping all that in double-quotes, you put it in a curlies block. It's easier to write and to read.

    UPDATE: I stand corrected. Think-o

      And totally useless. The OP is trying to generate code, not catch exceptions.
      For better or worse eval handles two very different roles:
      1. eval STRING is used to generate new code during run-time by evaluating the string during run-time.
      2. eval BLOCK is used for exception handling and is not not evaluated during run-time.
      No idea why something more obvious like "catch" was not used for eval blocks, but that oversight has certainly led to lots of confusion.

      Elda Taluta; Sarks Sark; Ark Arks