tags:

views:

104

answers:

3

Can someone tell me why the main does not find the methods generated by Class::Accessor in this very small and trivial example ?

These few lines of code fail with

perl codesnippets/accessor.pl
Can't locate object method "color" via package "Critter" at
codesnippets/accessor.pl line 6.

see the code:

#!/opt/local/bin/perl
# The whole Class::Accessor thing does not work !!

my $a = Critter->new;
$a->color("blue");
$a->display;
exit 0;

package Critter;
    use base qw(Class::Accessor );
    Critter->mk_accessors ("color" );

    sub display {
        my $self  = shift;
        print "i am a $self->color " . ref($self) . ", whatever this word means\n";
    }
+8  A: 

Your code is out of order. If you want the color accessor to be available, you need to invoke mk_accessors before you create your object and start doing stuff with it. For example:

package Critter;
use base qw(Class::Accessor);
Critter->mk_accessors("color");

sub display {
    my $self  = shift;
    print $self->color, ' ', ref($self), "\n";
}

package main;
my $c = Critter->new;
$c->color("blue");
$c->display;

More commonly, the Critter code would be in its own module (Critter.pm), and all of the mk_accessor magic would happen when your main script runs use Critter -- well before your script starts working with Critter and Varmint objects.

FM
Alternatively, one could wrap the package declaration in a BEGIN block to ensure it runs before attempting to instantiate the object.
Ether
Yes, the code is out of order, it was on purpose, as to have the main part at the beginning to see it easily, and bury the implementation and all class stuff at the bottom of the file.It is not on a separate file because i am writing a perl utility, and i want to distribute it as one perl script, without having to tell users to install all class packages files separately and set their perl5lib variable
+2  A: 

FM is giving you good advice. mk_accessors needs to run before the other code. Also, normally you'd put Critter in a separate file and use Critter to load the module.

This works because use has compile time effects. Doing use Critter; is the same as doing BEGIN { require Critter; Critter->import; } This guarantees that your module's initialization code will run before the rest of the code even compiles.

It is acceptable to put multiple packages in one file. Often, I will prototype related objects in one file, since it keeps everything handy while I am prototyping. It's also pretty easy to split the file up into separate bits when the time comes.

Because of this, I find that the best way to keep multiple packages in one file, and work with them as if I were using them, is to put the package definitions in BEGIN blocks that end in a true value. Using my approach, your example would be written:

#!/opt/local/bin/perl

my $a = Critter->new;
$a->color("blue");
$a->display;

BEGIN {
    package Critter;
    use base qw(Class::Accessor );

    use strict;
    use warnings;

    Critter->mk_accessors ("color" );

    sub display {
         my $self = shift;

         # Your print was incorrect - one way:
         printf "i am a %s %s whatever this word means\n", $self->color, ref $self;

         # another:
         print "i am a ", $self->color, ref $self, "whatever this word means\n";

    }

    1;
}
daotoad
I also though of the BEGIN block just later in the day i posted this question, and i will try it now.I did not want to put class package files as separate files because i am writing a perl utility, and i want to distribute it as one perl script, without having to tell users to install all class packages files separately and set their perl5lib variable
I was also wondering why the Critter->new() was working and not the $a->color("blue"), but your explanation of "use" internal working told me why the functions (eg new() ) inherited from the base class C:A were available.
+1  A: 

I just wanted to provide you with a better solution -- feel free to downvote this to oblivion if the solution isn't welcome, but C::A is really a bad idea this day and age, use Moose:

package Critter;
use Moose;

has 'color' => ( isa => 'Str', is => 'rw' ); # Notice, this is typed

sub display {
    my $self = shift;
    printf (
        "i am a %s %s whatever this word means\n"
        , $self->color
        , $self->meta->name
    );
}

package main;
use strict;
use warnings;

my $c = Critter->new;  # or my $c = Critter->new({ color => blue });
$c->color("blue");
$c->display;
Evan Carroll
Or Mouse, if you care about startup time.
MkV
I though Moose would not be available as RPM in default repositories of more conservative linux distributions such as centos, and made a quick test with C:A. But i see now Moose is in fact there, so i might use it. As my purpose is to make an easily distributable perl script, i try to avoid all modern and/or fancy modules, so that the script can run anywhere without having to get modules from cpan directly
`use Moose`, and `Module::Install` -- which can add the external packages into the module. Even, without `Module::Install`, `Moose` is probably very likely to be on the system of any decent Perl user. It will also make programming using perl much more enjoyable.
Evan Carroll