in reply to Creating flexible method accessor

The solution to your problem is to write \$self->${path}{qw($_)}, however, please consider the following:

  1. Don't use my $_ - either do for (...) {...} or for my $foo (...) {...} (lexical $_ is experimental)
  2. Always use warnings; use strict; (that'll give you the warning about my $_ as of Perl 5.18)
  3. Try very hard to avoid stringy eval - the current problems you are having with it are just the tip of the iceberg!

Instead of the string form of eval, in your case you can use closures:

sub mk_data_accessors3 { my $path = shift; for my $name (@_) { my $accessor = sub { carp "Warning: '$name' takes at most 2 arguments...\n" if +@_ > 2; my $self = shift; $self->{$path}{$name} = shift if @_; return $self->{$path}{$name}; }; # keep the scope of "no strict" as small as possible no strict 'refs'; # needed for *$name *$name = $accessor; # install the sub } } mk_data_accessors3('data','bar'); print bar({data=>{bar=>456}}), "\n"; # 456

That keeps the depth of $path limited to one level of the hash, but I'll leave the rest as an "exercise for the reader" :-)

Replies are listed 'Best First'.
Re^2: Creating flexible method accessor
by tobyink (Canon) on Feb 02, 2014 at 10:11 UTC

    Personally I'd stick with the stringy eval. An accessor created that way, would inline $path and $name instead of closing over them. Inlining will result in significantly faster accessors than closing over would.

    use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name

      s/significantly/insignificantly/ Plus you'll get proper file and line number information displayed in error messages and even be able to see the relevant source code in the debugger.

      - tye        

        I chose my words very particularly. The difference is not massive, but it is significant - i.e. it's consistently big enough to be detectable. For a very simple rw accessor closing over a single variable (that is, sub { $_[0]{$name} = $_[1] if @_ == 2; $_[0]{$name} }), it's likely to be about 10%.

        For the example which would close over two variables (sub { $_[0]{$path}{$name} = $_[1] if @_ == 2; $_[0]{$path}{$name} }) it's more like 15%.

        File and line number info is easy to add to stringy evals.

        Moose, Moo, etc create accessors this way not just for fun, but because they're measurably faster.

        use strict; use warnings; use Benchmark qw(cmpthese); my $name = 'foo'; my $path = 'foobar'; my $sub1 = sub { $_[0]{$path}{$name} = $_[1] if @_ == 2; $_[0]{$path}{ +$name} }; my $sub2 = eval qq[ sub { \$_[0]{$path}{$name} = \$_[1] if \@_ == 2; \ +$_[0]{$path}{$name} } ] or die($@); my $self = {}; cmpthese -1, { closure => sub { $self->$sub1(0); $self->$sub1( $self->$sub1 + $_ ) for 0..10_000; }, stringy => sub { $self->$sub2(0); $self->$sub2( $self->$sub2 + $_ ) for 0..10_000; }, };
        use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name
Re^2: Creating flexible method accessor
by puterboy (Scribe) on Feb 02, 2014 at 02:49 UTC
    Thanks for the help!
    Couple of responses/questions:
    1. I actually do use strict & warnings at the top of all of my scripts.
    2. I actually had a typo in my copying over my routines. I didn't use 'for my $_ (@_)', rather I used 'for (@_)' and then used '$_' to reference the current iterated array element which I thought was always ok. At least it doesn't give me errors in 5.10.0
    . 3. I like your way of not using eval.
    4. However, I wanted to be able to pass something more general than just 'data'. For example. I might want to access several layers down as in: $self->{data}->{level2}->{level3}' etc. More generally, I would like to pass an arbitrary 'path' to some sequence of hashes and arrays to get/set to the desired accessor element. (btw I am using this to access elements of a JSON-decoded string which typically can be multiple levels deep)
      Disregard #4. I misread what you wrote. It works fine.
        Actually, to clarify -- your suggestion of using ${path} works fine when applied to my original script which allows the multi-level hierarchies I need.

        However, it is not obvious (to me) how to make this work in your non-eval script. If I use ${path}, I get all sorts of errors.

        Unfortunately, the exercise you leave to the reader is actually the exercise that was the point of my original question :) (though I quite appreciate your provided script).

      for (@_) { whatever($_) } is fine, for my $whatever (@_) just helps readability in case the body of the loop grows to more than a few lines long. But one should stay away from my $_.

      It wouldn't hurt to upgrade your Perl if you can, 5.10.1 was released over 4 years ago. If you're on a multi-user system perhaps your sysadmin has already installed a newer version of Perl in a non-default location, such as under /opt/.

      I see this quite often on PerlMonks and I really don't understand why I see so many "typos when copying"... Ctrl+C, Ctrl+V ???