Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change

Creating flexible method accessor

by puterboy (Scribe)
on Feb 02, 2014 at 00:02 UTC ( #1073006=perlquestion: print w/replies, xml ) Need Help??
puterboy has asked for the wisdom of the Perl Monks concerning the following question:

I first created the following accessor that works:
sub mk_data_accessors { for my $_ (@_) { eval qq{ sub $_ { carp "Warning: '$_' takes at most 2 arguments.. +.\n" if \@_ > 2; my \$self = shift; \$self->{data}->{qw($_)} = shift if \@_; return \$self->{data}->{qw($_)}; } }; die $@ if $@; } }
which I call using:
mk_data_accessors(qw(method1 method2 method3));
I then tried to 'generalize' by allowing the 'path' to the method to be a variable as follows:
sub mk_data_accessors2 { my $path = shift; for my $_ (@_) { eval qq{ sub $_ { carp "Warning: '$_' takes at most 2 arguments.. +.\n" if \@_ > 2; my \$self = shift; \$self->$path{qw($_)} = shift if \@_; return \$self->$path{qw($_)}; } }; die $@ if $@; } }
which I called with:
mk_data_accessors2('{data}->', qw(method1 method2 method3));
This gave me errors about %path not being defined -- presumably because $path{qw{$_}} is being interpreted as a hash element.
I imagine the solution requires some additional level of 'eval' +/- quoting but I can't seem to figure this out.
Any suggestions?

Replies are listed 'Best First'.
Re: Creating flexible method accessor
by Anonymous Monk on Feb 02, 2014 at 01:57 UTC

    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" :-)

      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        

      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.

        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 ???

Re: Creating flexible method accessor
by puterboy (Scribe) on Feb 02, 2014 at 04:13 UTC
    One other question..
    I would like to define this subroutine in a package and then run the routine from other packages where I actually want the actual accessors to live. However, it seems that the mk_accessor routine creates all the accessors in the name-space of the package where the mk_accessor subroutine is defined rather than the one where it is run.

    Is there any easy/clean way to have the accessor subroutines created in the namespace of the package where mk_accessor is run?

      Sub::Install (recommended) or:

      my ($caller_package) = caller; ... no strict 'refs'; *{"${caller_package}::$name"} = $accessor;

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1073006]
Approved by ww
and snow settles gently...

How do I use this? | Other CB clients
Other Users?
Others contemplating the Monastery: (6)
As of 2018-01-22 23:17 GMT
Find Nodes?
    Voting Booth?
    How did you see in the new year?

    Results (238 votes). Check out past polls.