views:

60

answers:

3

How should I define a Moose object subroutine after its initialization?

I'm writing an object module using Moose and I plan to serialize (nstore) the created objects.

Examine the following (simplified!) example:

package MyObj 0.001;

use Moose;
use namespace::autoclean;

has 'size' => (
 is       => 'ro',
 isa      => 'Int',
 required => 1,
);

sub some_sub {
 my ($self, @more) = @_;
 if ($self->size() < 100) # do something;
 elsif (($self->size() < 500)) # do something else;
 elsif (($self->size() < 7500)) # do something else;
 # ...
}

1;

some_sub acts differently depending on size. Since size is read-only, it remains constant after the object has been initialized.

So, assuming I call some_sub zillion times, it's a pity that I have to go through all the ifs each time.

I'd better do this once after the object has been initialized, then set some_sub to be a simpler function with noifs at all.

But... how can I do that?

UPDATE

Perhaps I should add a lazy attribute of type subref that will hold a reference to the chosen subroutine. some_sub will then simply call $self->chosen_sub->(@_). What do you think?

A: 

Perhaps another case for MooseX::SingletonMethod! (Sorry I'm reading your questions in reverse order!).

For eg:

use 5.012;
use warnings;

package MyObj 0.001;
use MooseX::SingletonMethod;
use namespace::autoclean;

has 'size' => (
    is       => 'ro',
    isa      => 'Int',
    required => 1,
);

sub _which_sub {
    my ($self) = @_;

    if    ($self->size < 100)  { return sub{ 'A' } }
    elsif ($self->size < 500)  { return sub{ 'B' } }
    elsif ($self->size < 7500) { return sub{ 'C' } } 
    return sub { 'D' };
}


package main;

my $obj = MyObj->new( size => 200 );

$obj->add_singleton_method( some_sub => $obj->_which_sub );

say $obj->some_sub;  # => B


And it should be possible to add this single method creation from inside your class. Have a look at this blog post for some guidance: Moose Singleton Method: Now without roles!. And also a hotchpotch of posts here

/I3az/

draegtun
I don't like it so much, since I'd like this to be transparent to the user. This should all happen during the initialization of the object. Also see my update for another take on it.
David B
It could be made to be this transparent (the links provided give some insight into reblessing that needed for this). However if you are OK with `$self->chosen_sub->(@_)` (re: your update) then see my new answer for this.
draegtun
Is there a reason I shouldn't be OK with `$self->chosen_sub->(@_)`?
David B
Nope. Just referring to API, ie. happy with using `$obj->chosen_sub->(...)` over `$obj->chosen_sub(...)`
draegtun
BTW, I think a builder solution is the best approach for this.
draegtun
A: 

Regarding your update:

use 5.012;
use warnings;

package MyObj;
use Moose;
use namespace::autoclean;

has 'size' => (
    is       => 'ro',
    isa      => 'Int',
    required => 1,
);

has 'chosen_sub' => (
   is       => 'ro',
   isa      => 'CodeRef',
   lazy     => 1,
   builder  => '_build_chosen_sub',
   init_arg => undef, # unless want option of providing anon sub at construction?
);

sub _build_chosen_sub {
    my ($self) = @_;

    if    ($self->size < 100)  { return sub{ 'A' } }
    elsif ($self->size < 500)  { return sub{ 'B' } }
    elsif ($self->size < 7500) { return sub{ 'C' } } 
    return sub { 'D' };
}

package main;
my $obj = MyObj->new( size => 200 );
say $obj->chosen_sub->();  # => B

/I3az/

draegtun
Note that the native Code attribute trait has 'execute' and 'execute_method' delegates that can be used to easily set up a CodeRef attribute that acts like a common method.
phaylon
@phaylon: Cool, not seen that before. Have now!... http://search.cpan.org/dist/Moose/lib/Moose/Meta/Attribute/Native/Trait/Code.pm
draegtun
I think you can make 'chosen_sub` private (i.e. change to `_chosen_sub`) then add `sub mysub { my ($self) = @_; return $self->_chosen_sub()->(@_);}`. Now we can call `$obj->mysub();`. On second look, this is what hdp has written.
David B
Yes hdp answer is the best one (+1). I was thinking of writing the same but aimed to keep it closer to your update, ie. I thought you wanted/needed an anon sub in their.
draegtun
+4  A: 
has calculation_method => (is => 'ro', lazy_build => 1, init_arg => undef);

sub _build_calculation_method {
    my $self = shift;
    return '_calculate_small'  if $self->size < 100;
    return '_calculate_medium' if $self->size < 500;
    return '_calculate_large'  if $self->size < 7500;
    return '_calculate_enormous';
}

sub _calculate_small  { ... }
sub _calculate_medium { ... }
# etc.

sub calculate {
    my $self = shift;
    my $method = $self->calculation_method;
    return $self->$method(@_);
}

As a bonus, calculation_method is now serializable too.

hdp
+1 yup, that's pretty much what my update was about. Just wanted to make sure it makes sense.
David B
You could also fold the `calculate()` method into the `calculation_method` attribute (and rename as `s/_method//`) by making it `isa => 'CodeRef', traits => ['Code'], handles => { calculate => 'execute_method' });` (as phaylon noted in draegtun's answer).
Ether