Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

a simple matter of elegance

by spiros (Beadle)
on Oct 03, 2007 at 09:50 UTC ( [id://642355]=perlquestion: print w/replies, xml ) Need Help??

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

I turn to you wise men for a simple yet effective answer to the burden that has been within my head. Let us suppose that $self is a blessed reference and contains some data
$self->{'data'}->{'name'} = 'spiros'; $self->{'data'}->{'address'} = 'london';
In order to keep things tidy, I created the 'name' and 'address' sub which effectively return the values:
sub name { my $self = shift; return $self->{'data'}->{'name'}; }
Is there a more 'elegant' or 'dynamic' way of doing so. Clearly this solution is not scalable in the event that the data structure has, say, 50 attributes since it requires manual declaration. May your life always be in the light. Thank you for your replies :)

Replies are listed 'Best First'.
Re: a simple matter of elegance
by lima1 (Curate) on Oct 03, 2007 at 10:27 UTC
    You can do that with AUTOLOAD, for example with:
    package MyAutoloadDemo; use strict; use warnings; use Carp; our $AUTOLOAD; sub new { my $class = shift; my $self = {}; $self->{'data'}->{'name'} = 'spiros'; $self->{'data'}->{'address'} = 'london'; bless $self, $class; return $self; } sub AUTOLOAD { my $self = shift; my $type = ref $self; my $name = $AUTOLOAD; $name =~ s/.*://; # strip fully-qualified portion if (!exists $self->{data}->{$name} ) { croak "Can't access $name field in class $type"; } return $self->{data}->{$name}; } 1;
    use MyAutoloadDemo; my $demo = MyAutoloadDemo->new(); warn $demo->name; warn $demo->address; warn $demo->unknown;
    This prints:
    spiros at test.pl line 4. london at test.pl line 5. Can't access unknown field in class MyAutoloadDemo at test.pl line 6
    But you probably want something from CPAN's Class:: namespace, eg. Class::MethodMaker, Class::Accessor, Class::Std, ..
Re: a simple matter of elegance
by merlyn (Sage) on Oct 03, 2007 at 11:06 UTC
    I dislike redoing the "work" to locate $self->{data} each time there. I'd write that as:
    for ($self->{data}) { $_->{name} = 'spiros'; $_->{address} = 'london'; }
    or even
    for ($self->{data}) { @$_{qw(name address)} = qw(spiros london); }

      Or even

      @{$self->{data}}{qw(name address)} = qw(spiros london);
Re: a simple matter of elegance
by grinder (Bishop) on Oct 03, 2007 at 11:02 UTC

    I'm not sure I'm too keen on the AUTOLOAD-based suggestions. I would have almost suggested Object::Tiny, which is probably one of the nicest ways of doing what you want... except that it will fall apart on the fact that the elements in question are all hanging off a subkey. And I suspect most other packages of this nature will, as well.

    You could of course steal the code from Object::Tiny::import to inject the accessors and have them fetch the information from the correct part of your object. Or restructure the innards of your object to lift them up to the top level. Or delegate them to a sub-object... the possibilities are endless.

    • another intruder with the mooring in the heart of the Perl

Re: a simple matter of elegance
by andreas1234567 (Vicar) on Oct 03, 2007 at 10:30 UTC
    A very general approach:
    package foo; use strict; use warnings; sub new { my $class = shift; my $self = {}; $self->{data} = undef; bless ($self, $class); return $self; } # setter and getter sub data { my ( $self, $attr ) = @_; $self->{'data'}->{$attr} = $attr if (defined($attr)); return $self->{'data'}->{$attr}; } 1; __END__
    $ perl -l use foo; use strict; use warnings; my $foo = foo::->new(); $foo->data('bar', 42); print $foo->data('bar'); __END__ bar
    Update: Added to make a complete working sample.
    --
    Andreas
Re: a simple matter of elegance
by Fletch (Bishop) on Oct 03, 2007 at 13:02 UTC

    You may also be interested in something along the lines of Class::MethodMaker which will let you declare attributes and build your accessor subs for you (without resorting to AUTOLOAD evilness).

Re: a simple matter of elegance
by jeanluca (Deacon) on Oct 03, 2007 at 10:11 UTC
    I'm not sure I understand your question correctly, but I guess you just need samething like
    sub data { my ( $self, $attr ) = @_ ; return $self->{'data'}->{$attr} ; }

    Cheers
    LuCa
      Apologies, I should have been more clear. I want the sub names to be identical to the keys of the attributes which they fetch. For example:
      sub name would return $self->{'data'}->{'name'} sub address would return $self->{'data'}->{'address'}
      etc
        maybe perlsub can help
        sub AUTOLOAD { my ($self, $field ) = @_ ; (my $sub = $AUTOLOAD) =~ s/.*::// ; return $self->{'data'}->{$sub} ; }
        The code is not tested!

        LuCa
Re: a simple matter of elegance
by snoopy (Curate) on Oct 03, 2007 at 22:58 UTC
    You could create closures for each of the attributes and add them to the package's namespace:
    package Foo; use warnings; use strict; foreach my $att (qw/name address age sex geek_code/) { no strict 'refs'; *$att = sub { my $self = shift; return $self->{data}->{$att}; }; }
Re: a simple matter of elegance
by dragonchild (Archbishop) on Oct 03, 2007 at 15:28 UTC
    sub _data { $_[0]{data}{$_[1] } sub name { $_[0]->_data( 'name' ) } sub address { $_[0]->_data( 'address' ) }

    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: a simple matter of elegance
by tfrayner (Curate) on Oct 04, 2007 at 08:04 UTC
    Hi,

    I've not seen this suggested yet:

    { no strict qw(refs); foreach my $method ( keys %{ $self->{'data'} } ) { *{$method} = sub { my ( $self ) = @_; return $self->{'data'}->{$method}; }; } }
    I've used this a few times in the past to generate repetitive object accessors/mutators.

    Cheers, Tim

    Update: I beg your pardon, snoopy beat me to it :-)

Re: a simple matter of elegance
by frostman (Beadle) on Oct 05, 2007 at 00:44 UTC

    Unless you absolutely positively want exactly that internal data structure, you can solve the general problem with speed and elegance by using one of the Class::Accessor modules.

    That gives you a mature, extensible and (in my opinion) well-designed solution to the problem and others like it in your codebase.

    Of course, it's possible that this isn't dynamic enough for you, and that you already know Class::Accessor like the back of your hand and the CTO insists that the API is frozen and so on. But if not, I'd really recommend you use Class::Accessor (or Class::Accessor::Fast or Class::Accessor::Faster). I bet you could make a factory on top of that if you really need one.

    Anyhow, here's a simple example of that module in action:

    package Foo; use strict; use warnings; use base qw(Class::Accessor); __PACKAGE__->mk_accessors(qw(spiro london)); 1;

    Meanwhile, in a nearby t/*.t:

    my $foo = Foo->new( { spiro => 'Agnew', london => 'England' } ); isa_ok( $foo, 'Foo' ); is( $foo->spiro, 'Agnew', "spiro" ); is( $foo->london, 'England', "london" ); ok( $foo->spiro('Not really Agnew'), "set spiro" ); ok( $foo->london('Ontario'), "set london" ); is( $foo->spiro, 'Not really Agnew', "spiro" ); is( $foo->london, 'Ontario', "london" );

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://642355]
Approved by marto
Front-paged by Old_Gray_Bear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others imbibing at the Monastery: (4)
As of 2024-04-20 03:42 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found