http://www.perlmonks.org?node_id=510974

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

Hi all,

I know there has to be a module doing what I want, but I can't quite seem to find it.

Essentially, I'm looking for a module which lets me build simple getter/setters with an optional validation callback.

Class::MethodMaker
Has an annoying habit of failing tests
Class::Accessor
Must inherit from it. I don't want that.
Class::Accessormaker
Nice but no validation

I want something like this:

use Class::BuildMethods 'name', 'rank' => { default => 'private' }, 'date' => { default => $some_date, validate => &validate_date };

Those the the things I'm doing over and over again. Constantly. That's what I want to abstract out but I'm not finding something which provides that in one easy to use module. Writing this is trivial but I'm loathe to upload yet another friggin' module into the Class:: namespace. I know this module must be out there. What did I miss? :)

Cheers,
Ovid

New address of my CGI Course.

Replies are listed 'Best First'.
Re: A Class:: module (sample implementation)
by Ovid (Cardinal) on Nov 23, 2005 at 02:08 UTC

    While waiting for responses, I through together a small sample implementation. It assumes that you will set pass single arguments to setters:

    So far, no one's commented on the obvious bug. It's fixed in the current version I'm working on.

    package Class::BuildMethods; use strict; use warnings; my %value_for; sub import { my $class = shift; my ($callpack) = caller(); while (@_) { my $method = shift; my $constraints; my $validation_sub; if ( 'HASH' eq ref $_[0] ) { $constraints = shift; if ( exists $constraints->{default} ) { $value_for{$callpack}{$method} = delete $constraints-> +{default}; } if ( exists $constraints->{validate} ) { $validation_sub = delete $constraints->{validate}; } if ( my @keys = keys %$constraints ) { require Carp; Carp::croak( "Unknown constraint keys (@keys) for ${callpack}:: +$method"); } } no strict 'refs'; if ($validation_sub) { *{"${callpack}::$method"} = sub { my $self = shift; return $value_for{$callpack}{$method} unless @_; my $new_value = shift; $self->$validation_sub($new_value); $value_for{$callpack}{$method} = $new_value; return $self; }; } else { *{"${callpack}::$method"} = sub { my $self = shift; return $value_for{$callpack}{$method} unless @_; $value_for{$callpack}{$method} = shift; return $self; }; } } } 1;

    And the tests:

Re: A Class:: module I don't want to write
by perrin (Chancellor) on Nov 23, 2005 at 02:28 UTC
    I'd say fix MethodMaker, wrap Accessormaker, or suck it up and inherit from Accessor. No need to write a whole new thing.

      Problems:

      As for CMM, my response got long enough that I made it a meditation.

      Update: I finally got CMM to install and this failed with a "Can't coerce array into hash" message:

      { package Foo; use Class::MethodMaker [ scalar => 'name']; sub new { bless [], shift } } my $foo = Foo->new; use Data::Dumper; print $foo->name, $/; print Dumper $foo;

      If I switch that to a hashref, it works. A module adding methods to my class shouldn't make assumptions about the implementation.

      Cheers,
      Ovid

      New address of my CGI Course.

        Do what you think is best and explain your justifications in the documentation. It sounds like you should start from scratch. "I created this new module, because adding to the old modules wouldn't get the job done." IMHO, these "not invented here" commandments are guidelines that may not apply to every situation.

        I started using Class::Std to build getters and setters. Then, I discovered Object::InsideOut met my needs better. The author of Object::InsideOut explains why he created a similar module. I accepted his justifications. I have a colleague that says the author of Object::InsideOut should have improved Class::Std. But, I say the authors had his reasons and the result works better.

        Starting over is not always bad. But don't reinvent without a reason.
        Doesn't an accessor method generator have to make assumptions about how your object's data is stored? I don't see how it could access the data if it didn't. Support could be added to these modules for array-based objects and the like as separate options, but you'd still need to tell it what your object looks like, or else use a multi-level get/set like Class::Accessor so you can define that part in your code.
Re: A Class:: module I don't want to write
by siracusa (Friar) on Nov 23, 2005 at 13:11 UTC

    When I was in a similar situation, I decided to write my own simple method-maker. It's worked out well for me.

    (Edit: Just to clarify my reasoning)

    I also used Class::MethodMaker originally. The kinds of methods I wanted to create were not in the default set, so I ended up writing my own, which is something that Class::MethodMaker is designed to allow. But since the methods I wanted to create were very similar to some of the default Class::MethodMaker types, I thought I should sort of "subclass" those methods types by reusing some of their code--another thing that Class::MethodMaker allows.

    But the end result was pretty ugly. It was hard to look at my custom method types and understand what they actually did without thoroughly knowing the Class::MethodMaker internals. The methods were filled with tons of "macros" (special tokens understood by Class::MethodMaker) imported from the default method types. It worked, but it was kind of a mess.

    Then I thought a better approach would be to write my own method types from scratch instead of trying to reuse code bits from the default types. At that point, I began to consider exactly what I was gaining by using Class::MethodMaker. When Class::MethodMaker 2.x development started (I was using 1.x) and backwards compatibility was only promised to "mostly" work, I decided to just write my own, simple method maker that did exactly what I wanted. So that's what I did, and I uploaded it to CPAN (plus a simple object base class that I also use). Like I said, it's worked out well for me. If anyone else wants to use my method maker, that's great. If not, I'm still happy with it.

Re: A Class:: module I don't want to write
by Aristotle (Chancellor) on Nov 24, 2005 at 00:33 UTC

    I agree with what shotgunefx said over in your other thread.

    I think these modules are symptoms of a fundamental problem in Perl5.

    The basic task is about as trivial as a task gets, and always depends quite intimately on the other code you’re writing. In other words, this is not something that can be reused. What it is is something which should be transparently rewritable every time you need it, by virtue of the language supporting enough abstraction that no particular rewrite of the code duplicates logic to an appreciable extent. Accessor generation should be composable so directly of such basic operators that rewriting it would “duplicate” no more “logic” than writing $total = $price + $vat in one place in the code and $eta = $now + $runtime in another does.

    With Perl5, rewriting is doable, but a tad too verbose – hence the desire to package the code into a module, even though it is conceptually un-reusable.

    Hopefully the higher-level functional composition operators in Perl6 will make this sufficiently transparent.

    Makeshifts last the longest.

      "Conceptually un-reusable"? I re-use my method maker all over the place...basically, in every single other module I write. I'm not sure if that's "conceptual" or not, but it sure is re-use! :)

        Yes. A method maker is verbose enough in Perl5 that you will want to reuse it. As long as you can make a sufficient number of assumptions about the classes it gets used in, you can get some milage out of such reuse, too. These are things I already said.

        The point still stands that if the language offered enough abstraction, you would never even feel the urge to use a method maker. Consider that in Perl6, there will be a default OO system for which you will be able to declare attributes without writing code to implement them, and will be able to attach constraints to such attributes declaratively. For those cases where you need something really fancy, there will be functional composition constructs to make the job as simple as possible. Will there be any need for method makers?

        Aren’t method makers, then, an attempt to patch a deficiency in Perl5?

        Makeshifts last the longest.

Re: A Class:: module I don't want to write
by jdhedden (Deacon) on Nov 24, 2005 at 00:55 UTC
    Essentially, I'm looking for a module which lets me build simple getter/setters with an optional validation callback.
    Object::InsideOut does provide these capabilities.

    It might be beneficial to consider using a comprehensive support module like Object::InsideOut instead of trying to cobble together something barely workable out of a lot of disparate modules.


    Remember: There's always one more bug.
Re: A Class:: module I don't want to write
by metaperl (Curate) on Nov 25, 2005 at 02:36 UTC
    Whatever you do, don't rewrite the validation part. Dave Rolsky has done a super job with Params::Validate. I use that thing every day it seems. It's like bread and butter.

      I have already written and uploaded Class::BuildMethods. However, the validate key only points to a subref. You supply the validation code and Params::Validate is fine for that.

      Cheers,
      Ovid

      New address of my CGI Course.

Re: A Class:: module I don't want to write
by itub (Priest) on Nov 23, 2005 at 17:20 UTC
    Just write your own module that does things exactly like you want, and that you can rely on. Don't worry if someone else would want to use it or not. If it's good, they'll use it anyway. ;-)
Re: A Class:: module I don't want to write
by Anonymous Monk on Nov 23, 2005 at 21:58 UTC

      No. In wading through the Class:: heirarchy, I missed that. Of course, it fails the "simple" test and while it gives me a bit of flexibility on my object's internals, it still exposes them. This exposure means that once I've chosen my object's internal represenation, I have to switch to a different Class::MakeMethods subclass if I need to change them (and I've had to change internals before). I also don't see any explicit argument validation available.

      Cheers,
      Ovid

      New address of my CGI Course.