http://qs321.pair.com?node_id=770412

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

I'm doing OOP Perl, and I am growing tired of the following code:

sub set_main_message { $_[0]->{main_message} = $_[1]; } + sub get_main_message { return $_[0]->{main_message}; }

There are different approaches to providing this sort of functionality, in more concise ways (and with extra side benefits). One obvious approach is the "fields/metadata" approach where I make a little object. Could someone give me an overview of the different CPAN modules that solve this and pros/cons? I'm leaning toward something simple.

Replies are listed 'Best First'.
Re: "Fields" for "Objects"
by Your Mother (Archbishop) on Jun 10, 2009 at 18:24 UTC

    You should look at Moose. It's better OOP. If you're in a hurry you can also check out Class::Accessor which is passable but nowhere near as rich as Moose.

Re: "Fields" for "Objects"
by webfiend (Vicar) on Jun 10, 2009 at 19:10 UTC

    Listen to Your Mother.

    I thought you might be curious about the specifics of how the advice to use Moose would help in this case, though. The functionality of your code above would be replaced by:

    package Whatever; use Moose; has 'main_message' => ( is => 'rw' );

    Poof! Instant access to $whatever->main_message and $whatever->main_message($value).

Re: "Fields" for "Objects"
by stvn (Monsignor) on Jun 10, 2009 at 19:39 UTC
    Poof! Instant access to $whatever->main_message and $whatever->main_message($value).

    And if you dont like that style accessor there is MooseX::FollowPBP for Perl Best Practices style accessors (like you have in your example), MooseX::SemiAffordanceAccessor (for foo/set_foo style) and MooseX::Accessors::ReadWritePrivate which pretty much handles everything else (so the docs seem to indicate, I haven't used it myself).

    -stvn
Re: "Fields" for "Objects"
by vek (Prior) on Jun 10, 2009 at 22:52 UTC
    I usually write getter/setter combos:
    sub accountnumber { my ($self, $num) = @_; if (defined($num)) { $self->{account_num} = $num; return $self; } else { return $self->{account_num}; } }
    But even that's a pain in the arse too. So, Class::Accessor for the win.
    use base qw(Class::Accessor); __PACKAGE__->mk_accessors(qw(accountnumber));
    -- vek --
      It's interesting, this is how I write them too. But I noticed lodin's node, which contains an important but subtle difference; by doing this:
      sub field { my $self = shift; $self->{field} = $_[0] if @_; ... }
      instead of this:
      sub field { my ($self,$val) = @_; $self->{field} = $val if defined $val; ... }
      you don't run into issues where $self->field(undef) would silently return the old value without changing it, which isn't what you might expect. But because I've seen the second form elsewhere and it's technically better practice to assign @_ to variables I just stuck with it, but clearly the first way is better.
Re: "Fields" for "Objects"
by lakshmananindia (Chaplain) on Jun 11, 2009 at 03:33 UTC
    Class::Struct will also provide this accessors functionality
    --Lakshmanan G.

    The great pleasure in my life is doing what people say you cannot do.


Re: "Fields" for "Objects"
by Transient (Hermit) on Jun 10, 2009 at 18:15 UTC
    At the bottom of perltoot there is a nice way to use AUTOLOAD to generate the getters and setters for your objects. (The following code is directly from the link):
    package Person; use Carp; our $AUTOLOAD; # it's a package global my %fields = ( name => undef, age => undef, peers => undef, ); sub new { my $class = shift; my $self = { _permitted => \%fields, %fields, }; bless $self, $class; return $self; }
    sub AUTOLOAD { my $self = shift; my $type = ref($self) or croak "$self is not an object"; my $name = $AUTOLOAD; $name =~ s/.*://; # strip fully-qualified portion unless (exists $self->{_permitted}->{$name} ) { croak "Can't access `$name' field in class $type"; } if (@_) { return $self->{$name} = shift; } else { return $self->{$name}; } }
    Which is basically going to "auto build" anything that's set in your permitted hash. I've used this before and it's saved a great deal of time (and tediousness).

    I know this is a different approach than what you asked for (i.e. a specific module), but I hope it helps. It also combines your calls for get/set into a single method.

      Honestly, use of AUTOLOAD for this kind of stuff is just really a bad idea. You will outgrow this solution all too quickly, not to mention the fact that your code is broken once you introduce inheritance since you have no mechanism to inherit the fields. Sure that problem is solve-able but not without a bunch of tedious plumbing as well. It should also be noted that AUTOLOAD introduces something like a 400% performance penalty over normal method calls (which only gets bigger the more complex your AUTOLOAD gets).

      -stvn
        It should also be noted that AUTOLOAD introduces something like a 400% performance penalty over normal method calls

        That penalty can be avoided for every but the first call to the autoloaded setter/getter function, if the AUTOLOAD block generates a subroutine living in a typeglob, just as AutoLoader does:

        sub AUTOLOAD { my $self = shift; my $type = ref($self) or croak "$self is not an object"; my $name = $AUTOLOAD; $name =~ s/.*://; # strip fully-qualified portion unless (exists $self->{_permitted}->{$name} ) { croak "Can't access `$name' field in class $type"; } my $sub = sub { my ($self,$value) = @_; $self->{$name} = $value if defined $value; $self->{$name}; }; { no strict 'refs'; *{$name} = $sub; } goto $sub; }

        Each subsequent call to the method accesses the sub normally, i.e. through typeglob lookup.

        not to mention the fact that your code is broken once you introduce inheritance since you have no mechanism to inherit the fields. Sure that problem is solve-able but not without a bunch of tedious plumbing as well.

        That plumbing amounts to writing a proper import() subroutine which calls AUTOLOAD for the inherited fields and exports them to the inheriting class. But then, you're right, why reinvent the wheel (there are reasons, though) if there are plenty modules out there which handle that. There's Moose, classes, Class::Accessor, ...

        The only good reason to use AUTOLOAD is when you already know you need to use it. It has a magic-from-afar type behavior that can be weird.
        Danny.

      I second stvn.

      Since you know which fields are permitted when the class compiles you can generate them and get rid of AUTOLOAD. AUTOLOAD is really only needed when you do not know which methods the class will need to handle.

      my @fields = qw/ name age peers /; for my $name (@fields) { my $code = sub { my $self = shift; $self->{$name} = $_[0] if @_; return $self->{$name}; }; no strict 'refs'; *$name = $code; }
      Now your class acts exactly like it would have acted if you manually wrote the methods, and you pay no run-time penalties.

      Note how simple the logic of the code is compared to AUTOLOAD. And the AUTOLOAD implementation will get more complicated still. Consider what happens when you

      • do $obj->can('age'),
      • refactor and create a superclass that also uses AUTOLOAD, or
      • use multiple inheritance where another class also uses AUTOLOAD.
      • (Or forget to do return if $name eq 'DESTROY';.)
      A proper implementation of AUTOLOAD requires an awful lot of thinking, and still it does not buy you anything, rather the opposite, if you already know the method names.

      lodin

Re: "Fields" for "Objects"
by mamboking (Monk) on Jun 11, 2009 at 13:44 UTC
    You may want to reconsider why you're using so many accessors. More often than not you're violating encapsulation. Check out this article for more information: Accessors Are Evil.
Re: "Fields" for "Objects"
by cbrandtbuffalo (Deacon) on Jun 11, 2009 at 14:32 UTC
    Another vote for Class::Accessor. The author also added a 'follow_best_practice' mode a while ago so you can create getters and setters following the recommendation in Damian's book.
Re: "Fields" for "Objects"
by targetsmart (Curate) on Jun 11, 2009 at 03:53 UTC
    you can get this functionality with the help of AUTOLOAD, see Re: autoload usage
    This code could be seen in the examples of the book 'Advanced Perl Programming' by sriram srinivasan.

    Vivek
    -- In accordance with the prarabdha of each, the One whose function it is to ordain makes each to act. What will not happen will never happen, whatever effort one may put forth. And what will happen will not fail to happen, however much one may seek to prevent it. This is certain. The part of wisdom therefore is to stay quiet.