tags:

views:

113

answers:

5

Hi ,

I am new to object oriented Perl and i have to access member variable of same object in another subrutine of same object. Sample code is here :

use Class::Struct;

struct Breed =>
{
    name  => '$',
    cross => '$',
};

struct Cat =>
[
    name     => '$',
    kittens  => '@',
    markings => '%',
    breed    => 'Breed',
    breed2 => '$',

];

my $cat = Cat->new( name     => 'Socks',
                    kittens  => ['Monica', 'Kenneth'],
                    markings => { socks=>1, blaze=>"white" },
                    breed    => { name=>'short-hair', cross=>1 },
                    ** //breed2 => sub { return $cat->breed->name;}**

                  );

print "Once a cat called ", $cat->name, "\n";
**print "(which was a ", $cat->breed->name, ")\n";**
print "had two kittens: ", join(' and ', @{$cat->kittens}), "\n";

But i am not sure how to use that $cat->breed->name in subroutine for breed2 ? Can some one help me with this.

+1  A: 

Don't use Class::Struct use Moose.

package Breed;
use Moose;
has 'name'  => ( isa => 'Str', is => 'ro', required => 1 );
has 'cross' => ( isa => 'Bool', is => 'ro' );

package Cat;
use Moose;
has 'name'     => ( isa => 'Str', is => 'ro', required => 1 );
has 'kittens'  => ( isa => 'ArrayRef[Cat]', is => 'ro' );
has 'markings' => ( isa => 'HashRef', is => 'ro', default => sub { +{} } );
has 'breeds'   => ( isa => 'ArrayRef[Breed]', is => 'ro' );

package main;
use Modern::Perl;
my $cat = Cat->new({
  name       => 'Socks',
  , kittens  => [ Cat->new({name=>'Monica'}), Cat->new({name=>'Kenneth'}) ]
  , markings => { socks=>1, blaze=>"white" }
  , breeds   => [ Breed->new({ name=>'short-hair', cross => 1 }) ]
});

say "Once a cat called ", $cat->name;
say "Which was a:";
say "\t".$_->name for @{$cat->breeds};
say "had kittens:";
say "\t".$_->name for @{$cat->kittens};

In this scheme, a cat can have any number of Breeds, and a Cat can have any number of kittens which are also objects of Cat.

update to solve your problem specifically

  1. You can make it implicit in the constructor the second breed is the first if it isn't supplied.

    package Cat; sub BUILD { my $self = shift; $self->breeds->[1] = $self->breeds->[0] if $self->breeds->[0] && ! $self->breeds->[1] }

  2. You can pass in a token that identifies it as such, in the constructor (this should be easy but I can add an example if you want)

  3. You can make Cat understand that if there is only one breed then both of the parents are the same

    package Cat; sub is_pure_bred { length @{$_[0]->breeds} == 1 ? 1 : 0 }

  4. You can make ignore the breed of the cat, by setting it to undef, and determine the breed by that of the parents. This is because your breed is always a function of your lineage anyway. You can constraint this in a Moose trigger, the cat either requires two cat parents, or it requires a breed.

footnote Moose objects serialize fairly nice with XXX too:

... use XXX; YYY $cat;

--- !!perl/hash:Cat
breeds:
  - !!perl/hash:Breed
    cross: 1
    name: short-hair
kittens:
  - !!perl/hash:Cat
    markings: {}
    name: Monica
  - !!perl/hash:Cat
    markings: {}
    name: Kenneth
markings:
  blaze: white
  socks: 1
name: Socks
...
Evan Carroll
-1. The OP asked for how to do it with Class::Struct, which is a perfectly fine OO framework. Moose is great, but IMHO this answer is counterproductive.
friedo
Evan Carroll
oh, and thanks for at least telling me /why/ you downvoted.
Evan Carroll
Don't you mean camel balls? :)
friedo
"use Moose" is the Perl-specific version of the "use jQuery" meme on SO.
Adam Bellaire
This doesn't really answer the actual question. The problem was to provide a shortcut method that refers to part of the object before you create it. Moose is nice, but it still doesn't solve the problem.
brian d foy
Check the comments, but I'll update the answer.
Evan Carroll
+2  A: 

You can't use $cat->breed->name inside the Cat constructor. But you can define breed2() as a method after the constructor:

sub Cat::breed2 {
    my ($self) = @_;
    return $self->breed->name;
}
Christophe Vu-Brugier
+3  A: 

The problem in breed2 is that you are trying to refer to a variable that you haven't defined yet. It looks like it is the same name, but it's not the object you are creating. It's a bit of a chicken-and-egg problem.

I'm not so sure that you want an anonymous subroutine like that in that slot anyway. Are you just trying to shorten $cat->breed->name to $cat->breed2? You can start with undef in breed2 and change its value right after the constructor since you'll have the reference to the object then. However, even if you put a subroutine there, you have to dereference it:

my $cat = Cat->new( name     => 'Socks',
                    kittens  => ['Monica', 'Kenneth'],
                    markings => { socks=>1, blaze=>"white" },
                    breed    => { name=>'short-hair', cross=>1 },
                    breed2   => undef,

                  );
$cat->breed2( sub { $cat->breed->name } );

print "Once a cat called ", $cat->name, "\n";
print "(which was a ", $cat->breed2->(), ")\n";
print "had two kittens: ", join(' and ', @{$cat->kittens}), "\n";
brian d foy
+1  A: 

First, I'll start with several comments, then I'll get to the meat of your question.

OO Perl is a bit different than other OO systems. There is a very thin layer of basic support for OO that makes it possible to make your objects do just about anything you want. On the down side, you can make your objects do just about anything you want. Classical OO Perl involves a lot of boilerplate code, as you implement accessors and mutators for each attribute, perhaps add type checking and so forth. This has given rise to a wide variety of tools to automate the production of boilerplate code.

There are three ways that I approach OO Perl: Moose, classical hash based all hand coded, and Class::Struct. Moose is great for systems where you have complex needs, but it has a big impact on app start-up time. If launch time is important for your application, Moose is, for now, out of the question. Class::Struct is a great way to get a lowest common denominator, quick, simple OO app together, on the downside it doesn't support inheritance. This is where hand coded OOP comes in. If Moose or Class::Struct aren't viable options for one reason or another, I fall back on the basics. This strategy has worked well for me. The only change I have felt the need to make over the last few years, is to add Moose to my standard toolkit. It's a welcome addition.

Damian Conway's Object Oriented Perl is an amazing book that clearly explains OOP, how OO Perl works, and how to build objects that can do amazing things. It's a bit dated, but the book still holds up. Any serious student of OO Perl should read this book.

Now, for your question--

It looks to me like breed2 is not an attribute of your object, it is instead a method.

use Class::Struct;
use strict;
use warnings;

struct Breed =>
{
    name  => '$',
    cross => '$',
};

struct Cat =>
[
    name     => '$',
    kittens  => '@',
    markings => '%',
    breed    => 'Breed',
];

my $cat = Cat->new( name     => 'Socks',
                    kittens  => ['Monica', 'Kenneth'],
                    markings => { socks=>1, blaze=>"white" },
                    breed    => { name=>'short-hair', cross=>1 },
                  );

# Delegate to Breed::name
sub Cat::breed2 {
    my $self = shift;

    my $breed = $self->breed;  # Get the breed object

    my $name;

    eval { $name = $breed->name(@_) };

    warn "No breed specified for ".( $self->name )."\n"
        unless defined $name;

    return $name;
}

print  "Once a cat called ", $cat->name, "\n",
       "(which was a ", $cat->breed2, ")\n",
       "had two kittens: ", join(' and ', @{$cat->kittens}), "\n";

Things get a bit hairier if you want to keep a set of pre-defined breeds, and have breed2 select a breed object by name if no value is set.

This stripped down Cat implementation uses class data to keep track of allowed cat breeds, and

package Cat;
use strict;
use warnings;
use Carp qw( croak );

my %breeds = map { $_->{name}, Breed->new( %$_ ) } (
    { name=>'short-hair', cross=>1 },
    { name=>'long-hair', cross=>1 },
    { name=>'siamese', cross=>0 },
);

sub new {
    my $class = shift;
    my %args = @_;

    my $self = {};
    bless $self, $class;

    for my $arg ( keys %args ) {
        $self->$arg( $args{$arg} ) if $self->can($arg);
    }

    return $self;
}

sub breed {
    my $self = shift;
    if( @_ ) {
        my $v = shift;
        croak "Illegal cat breed" unless eval {$v->isa( 'Breed' ) };
        $self->{breed} = $v;
    }

    return $self->{breed};
}

sub breed2 {
    my $self = shift;

    my @breed_args;

    if( @_ ) {
        my $v = shift;

        croak "$v is not a supported breed\n" 
            unless exists $breeds{$v};

        @breed_args = ( $breeds{$v} );
    }

    my $breed = $self->breed(@breed_args);

    return unless $breed;
    return $breed->name;
}

Now, lets look at a Moose solution that uses all sorts of advanced goodies like type coercion and overloading:

BEGIN {
    package Breed;
    use Moose;

    has 'name'  => ( isa => 'Str',  is => 'ro', required => 1 );
    has 'cross' => ( isa => 'Bool', is => 'ro', required => 1 );

    use overload '""' => \&_overload_string;

    sub _overload_string {
        my $self = shift;

        return $self->name;
    }

    __PACKAGE__->meta->make_immutable;    
    no Moose;
    1;
}

BEGIN {
    package Cat;

    use Moose;
    use Moose::Util::TypeConstraints;
    use Carp;

    subtype 'MyTypes::CatBreed' => as class_type('Breed');

    coerce 'MyTypes::CatBreed' => 
        from  'Str' 
        => via  { Cat->supported_breed_by_name( $_ ) };



    has 'name'     => ( isa => 'Str',  is => 'rw', required => 1 );
    has 'kittens'  => ( 
        traits      => ['Array'],
        is          => 'ro',
        isa         => 'ArrayRef[Str]',
        default     => sub{ [] },
        handles     => {
           all_kittens   => 'elements',
           add_kittens   => 'push',
           get_kitten    => 'get',
           count_kittens => 'count',
           has_kittens   => 'count',
       },
    );
    has 'markings' => ( 
        traits      => ['Hash'],
        is          => 'ro',
        isa         => 'HashRef[Str]',
        default     => sub{ {} },
        handles     => {
            set_marking    => 'set',
            get_marking    => 'get',
            has_marking    => 'exists',
            all_markings   => 'keys',
            delete_marking => 'delete',
        },
    );
    has 'breed'    => ( 
        isa    => 'MyTypes::CatBreed', 
        is     => 'ro', 
        coerce => 1,
    );

    my %breeds;
    sub supported_breed_by_name {
        my $class = shift;
        my $name  = shift;

        croak 'No breed name specified' 
            unless defined $name and length $name;

        return $breeds{$name};
    }

    sub add_breed {
        my $class = shift;
        my $breed  = shift;

        croak 'No breed specified' 
            unless eval { $breed->isa('Breed') };

        croak 'Breed already exists'
            if exists $breeds{$breed};

        $breeds{$breed} = $breed;

        return $class;
    }

    sub delete_breed {
        my $class = shift;
        my $name  = shift;

        croak 'No breed name specified' 
            unless defined $name and length $name;

        return delete $breeds{$name};
    }

    __PACKAGE__->meta->make_immutable;    
    no Moose;
    1;
}


# Set up the supported breeds
Cat->add_breed($_) for map Breed->new( %$_ ), (
    { name=>'short-hair', cross=>1 },
    { name=>'long-hair', cross=>1 },
    { name=>'siamese', cross=>0 },
);

# Make a cat
my $cat = Cat->new( name     => 'Socks',
                    kittens  => ['Monica', 'Kenneth'],
                    markings => { socks=>1, blaze=>"white" },
                    breed    => 'short-hair',
);

print 
    "Once a cat called ", $cat->name, "\n",
    "(which was a ", $cat->breed, ")\n",
    "had ", , " kittens: ", join(' and ', @{$cat->kittens}), "\n";
daotoad
+1  A: 

You can fix this in a few ways, here are two of them:

use warnings;
use strict;

sub say {print @_, "\n"}

use Class::Struct;

struct Breed =>
{
    name  => '$',
    cross => '$',
};

struct Cat =>
[
    name     => '$',
    kittens  => '@',
    markings => '%',
    breed    => 'Breed',
    breed2   => '$',

];

sub Cat::breed_name {shift->breed->name}  #create a new accessor method

my $cat; # or declare $cat first
$cat = Cat->new( name     => 'Socks',
                    kittens  => ['Monica', 'Kenneth'],
                    markings => { socks=>1, blaze=>"white" },
                    breed    => { name=>'short-hair', cross=>1 },
                    breed2 => sub { return $cat->breed->name;},
                    # this is now ok, but a bit awkward to call
                  );

print "Once a cat called ", $cat->name, "\n";
print "(which was a ", $cat->breed2->(), ")\n";  #returns then calls code ref
print "(which was a ", $cat->breed_name, ")\n";  #new accessor method
print "had two kittens: ", join(' and ', @{$cat->kittens}), "\n";

The reason your closure did not work right is because you can not close over a variable that is defined in the current statement. When the sub {...} tried to close around $cat it couldn't because it was not in scope yet. The solution is simply to predeclare the variable.

However, it doesn't seem like Class::Struct lets you install methods that way cleanly. Instead, adding a new accessor method to the Cat:: package lets you call the method as you would expect.

Eric Strom
even if it did, you would have to autobox the subref or overload it in string context in order to get what you're wanting. And, if $cat is referring to the instance of the cat, just use +shift from inside the method to get $self... `breed2 => sub { +shift->breed->name }`
Evan Carroll
@Evan => the point is that you can't install methods that way with `Class::Struct`. you can't put it in the struct definition as you describe, so it has to go in the instance declaration, which will return it, not call it. to get your proposed method to work, you would have to call it like `$cat->breed2->($cat)`, which is a tad redundant for my tastes, not to mention unintuitive. also, the `+` is entirely unnecessary, that syntax is only needed with a bare `shift` when inside the curlies of a hash, since it is all `\w` characters and would look like a string to perl
Eric Strom
You're right, I wasn't thinking when I wrote that.
Evan Carroll