Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

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?

Comment on Creating flexible method accessor
Select or Download Code
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" :-)

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

      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        

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?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others musing on the Monastery: (12)
As of 2014-09-30 19:10 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    How do you remember the number of days in each month?











    Results (381 votes), past polls