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

Generic accessor for inside-out objects...

by Anonymous Monk
on Dec 10, 2007 at 21:44 UTC ( #656240=perlquestion: print w/replies, xml ) Need Help??

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

I have a module implemented as an inside-out object, and I want to generically expose all of its properties (as read-only), via an accessor method. Following the naming conventions from Perl Best Practices, all properties are named %dbh_of, %csv_of, %working_dir_of, etc. As such, I wrote the following method:
sub get { my($self,$attr) = @_; my $return_val; # if valid attribute, assign it as a return value if($attr =~ m/\A\w+\z/) { my $eval_string = sprintf '$return_val = $%s_of{refaddr $self};', +$attr; eval $eval_string; } # otherwise, carp a warning else { carp("Invalid attribute '$attr'"); } return $return_val; }
The idea behind this is that, given a string value of the name of a property (such as 'working_dir'), it would return the value of that property for the current instance (the value of %working_dir_of{refaddr $self}). This is not working, however, as all I get back from the method is undefs. I've tried replacing the string eval with a symbolic reference, even though IIRC, this could be exploited if attr is provided a raw memory address:
if($attr =~ m/\A\w+\z/) { $return_val = ${$attr}{refaddr $self}; }
Even with this, I'm still getting undefs returned...Any idea what I'm doing wrong, or (better yet) is there a simpler and better way?

Replies are listed 'Best First'.
Re: Generic accessor for inside-out objects...
by Joost (Canon) on Dec 10, 2007 at 21:47 UTC
      I don't understand the difference though, since the get method is in the scope of the object, and the string eval (or symbolic reference) is in the get method?

      It's not that I'm doubting or arguing with you, I just don't understand what nuance of the language I'm missing.

        Lexical variables are determined at compile time:
        # file use strict; package my $bee = 2; # lexical variable sub foo { return $bee++; }
        Assuming that's all in that file, Foo::foo() is the only subroutine with a handle on the lexical $bee. Note that foo() even keeps that variable available when $bee goes "out of scope". this is very important in understanding what's really going on. You'll want to do a search on "perl closures".

        That means there is no way to get at $bee except via Foo::foo(). Say:

        # some other file use strict; package Bar; sub bar { return $bee; }
        That fails because there is no $Bar::bee variable, but also, there is no way to specify you actually meant the $bee variable bound to the Foo::foo function.

        Note that package (or "global") variables can be accessed from other scopes:

        # file package Foo; our $blah = 2; # or use vars
        #file package Bar; print $Foo::blah;
Re: Generic accessor for inside-out objects...
by shmem (Chancellor) on Dec 10, 2007 at 22:41 UTC
    It all depends where you've declared e.g. %working_dir_of:
    { my %working_dir_of; } sub get { ... }

    won't work, but

    my %working_dir_of; sub get { ... }

    does: accessor and declaration have to be in the same scope. In principle, the string eval technique works, as seen with this snippet

    #!/usr/bin/perl use strict; my %hash = ( foo => 'bar'); sub meth { my ($h,$k) = @_; my $string = sprintf 'print $%s{%s},"\n"', $h, $k; eval $string; }; meth('hash','foo'); __END__ bar

    but it looks a bit whacky and eval is costly, too. Why is it deemed good practice to name an attribute e.g. dbh and the corresponding inside out attribute hash %dbh_of ? Looks to me like calling for trouble.

    If you want to stick with that "good practice", I'd recommend to set up a property hash keyed by the real attribute names, and having as values references to the attribute hashes:

    my( %dbh_of, %dir_of, ...); my %props = ( dbh => \%dbh_of, dir => \%dir_of, ... ); sub get { my($self,$attr) = @_; my $return_val; # if valid attribute, return the object's value if( defined $props {$attr} ) { return $props {$attr} -> { refaddr $self}; } # otherwise, carp a warning else { carp("Invalid attribute '$attr'"); } }

    Note that I've dropped the return value in case of failure; returning nothing at all in this case is better than returning an undefined value IMHO.

    Have a look at Alter for a simpler, faster and cleaner approach to insiteoudish object data encapsulation (e.g. no hassle with DESTROY).


    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
Re: Generic accessor for inside-out objects...
by kyle (Abbot) on Dec 10, 2007 at 22:41 UTC

    Perhaps the problem is somewhere else in your code. Filling in the blanks a little, it seems to work for me.

    package Foo; use Scalar::Util qw( refaddr ); use Carp; my %a_of; my %b_of; sub new { my $self = \do{my $x}; bless $self, __PACKAGE__; $a_of{ refaddr $self } = 'a: ' . rand; $b_of{ refaddr $self } = 'b: ' . rand; return $self; } sub puke { my $self = shift; print $a_of{ refaddr $self }, "\n"; print $b_of{ refaddr $self }, "\n"; return; } sub get { # pasted from OP } package main; my $o = Foo->new(); $o->puke(); print 'a --> ', $o->get('a'), "\n"; print 'b --> ', $o->get('b'), "\n"; print 'c --> ', $o->get('c'), "\n"; __END__ a: 0.917827691956521 b: 0.0455599141746177 a --> a: 0.917827691956521 b --> b: 0.0455599141746177 Use of uninitialized value in print at /home/kyle/ line 58 +. c -->
    If you're trying to write one get method in some other package and import it into each inside-out class, that won't work for the reasons Joost has already mentioned.

    Instead, maybe you should use the access methods available on the object. I did something like that in Make an inside-out object look hash-based using overload..

    sub FETCH { my ($self, $key) = @_; my $getter = "get_$key"; return $self->$getter(); }

    This way, you don't expose any private attributes, and you go through the proper access method, which might have some code in it more complicated that returning the attribute raw.

      I wonder if the original poster is running on a mac? I'm running osx, and when running the given example script, i get:
      a: 0.233512969994084 b: 0.77414042744519 a --> b --> c -->
      just a thought, perhaps that helps.

      Update: Ah, sorry. Temporary case of PEBKAC :o

      Systems development is like banging your head against a wall...
      It's usually very painful, but if you're persistent, you'll get through it.

        The code I put in my node won't run verbatim. I was trying to save space when I wrote:

        sub get { # pasted from OP }

        Indeed, if I run it as it is, it gives the results you show. However, even on the Mac I tried (OS X 10.4.11), it works when I take the code for get from the OP.

Re: Generic accessor for inside-out objects...
by parv (Vicar) on Dec 10, 2007 at 22:12 UTC
    my $eval_string = sprintf '$return_val = $%s_of{refaddr $self};', +$attr;

    I suppose $%s_of{...} is just a typo.

    Update: Never mind, completely missed sprintf() there.

Log In?

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

How do I use this? | Other CB clients
Other Users?
Others contemplating the Monastery: (6)
As of 2021-04-15 13:06 GMT
Find Nodes?
    Voting Booth?

    No recent polls found