5

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";
    }
Alex F
  • 826
  • 8
  • 18
  • I just tried and saw the same behavior with Class::Accessor::Classy and Mouse. – Alex F Jun 04 '10 at 11:34
  • Don't use Mouse or C:A, use `Moose` – Evan Carroll Jun 04 '10 at 15:16
  • See [my solution](http://stackoverflow.com/questions/2973549/perl-classaccessor-failure-trivial-example-why/2975492#2975492) for an example of this in `Moose`. – Evan Carroll Jun 04 '10 at 15:25
  • 1
    Initialization code needs to run to create the object methods. If the init code hasn't run, then the methods don't exist, and you will get this error. – daotoad Jun 04 '10 at 15:33
  • Evan Carroll: what is wrong with Mouse? Other than the fact that the Moose guys try to disown it and FUD it. – MkV Jun 04 '10 at 18:30
  • 1
    @james2vegas, the Mouse guys do to *Use Moose instead of Mouse.* (Mouse docs). And, the Mouse guys are more often than not the Mouse guys -- nothingmuch, and sartak do sizeable work in both. Last I checked, Sartak wasn't speaking too high of Mouse any more. I think the endplan is to have precompiled metaclasses and kill off Mouse. – Evan Carroll Jun 04 '10 at 18:36
  • 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 – – Alex F Jun 07 '10 at 02:04

3 Answers3

8

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.

FMc
  • 41,963
  • 13
  • 79
  • 132
  • 1
    Alternatively, one could wrap the package declaration in a BEGIN block to ensure it runs before attempting to instantiate the object. – Ether Jun 04 '10 at 16:08
  • 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 – Alex F Jun 07 '10 at 01:53
3

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
  • 26,689
  • 7
  • 59
  • 100
  • 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 – Alex F Jun 07 '10 at 01:56
  • 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. – Alex F Jun 07 '10 at 02:03
2

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
  • 78,363
  • 46
  • 261
  • 468
  • Or Mouse, if you care about startup time. – MkV Jun 04 '10 at 18:31
  • 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 – Alex F Jun 07 '10 at 01:59
  • `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 Jun 08 '10 at 15:10