For Programmers: Free Programming Magazines  


Home > Archive > PERL Beginners > November 2006 > Perl Inheritance(take-2)









You are viewing an archived Text-only version of the thread. To view this thread in it's original format and/or if you want to reply to this thread please [click here]

 

Author Perl Inheritance(take-2)
Muttley Meen

2006-11-03, 7:57 am

Hi!
Sorry if I double post, but the maillserver seems to have something against
perl scripts attachements, so my first email was droped by the MTA : /
Maybe if I put the code inline will be ok

In the attached script I implement two packages:
Package1 and Package2.

Package2 is derived from Package1, which I guess I dont well.
Now Package1 has a method called IncErr which increments a variable named $err.

If I call something like:

$a = Package1::new();
$a->IncErr();
print "ERR1: $a->{err}\n"; # should print 1

all goes well, but if I call something like:

$a = Package1::new();
$a->Package2->Create(); # this should call IncErr too
print "ERR1: $a->{err}\n"; # should print 1
doent't work as I expected.

Is there something wrong with the way I `bless`-ed the class Package2 ?



------>8-----------
#! /usr/bin/perl
package Package1 ;
sub new {
my ($class) = @_;
$class = __PACKAGE__ ;

print "Call new [".$class."]\n" ;
$r = [];
$this = {};
$class->{err} = 0 ;

$r->[0] = Package2::new($this->{sock_fd} );

bless $this, $class;
return $this ;
}

sub IncErr {
my $this = shift ;
$this->{err} += 1 ;
}

sub Package2 {
my $this = shift ;
@_ ? ($r->[0] = shift) : $r->[0];
}






package Package2 ;
@ISA = ("Package1");
sub new {
my ( $this, $sock ) = @_ ;
$class = __PACKAGE__ unless @_;
$this->{sock_fd} = $sock ;
bless $this;
return $this ;
}

sub Create {
my $this = shift;
print "Call Create\n" ;
print "ERR2: $this->{err} ( this should print 2 )\n" ;
$this->IncErr() ;
print "ERR2: $this->{err} ( this should print 3 )\n" ;
}

my $a = Package1::new();
$a->IncErr();
$a->IncErr();
$a->Package2->Create();
print "ERR1: $a->{err} ( this should print 3 )\n" ;

------------------->8--------------------
Mumia W.

2006-11-03, 7:57 am

On 11/03/2006 02:23 AM, Muttley Meen wrote:
> Hi!
> Sorry if I double post, but the maillserver seems to have something against
> perl scripts attachements, so my first email was droped by the MTA : /
> Maybe if I put the code inline will be ok
>
> In the attached script I implement two packages:
> Package1 and Package2.
>
> Package2 is derived from Package1, which I guess I dont well.
> Now Package1 has a method called IncErr which increments a variable
> named $err.
>
> If I call something like:
>
> $a = Package1::new();
> $a->IncErr();
> print "ERR1: $a->{err}\n"; # should print 1
>
> all goes well, but if I call something like:
>
> $a = Package1::new();
> $a->Package2->Create(); # this should call IncErr too


This is not allowed because "Package2" is not a method within the class
Package1. You want this:

my $b = Package2->new();
$b->Create();
print "ERR(\$b): $b->{err}\n";

> print "ERR1: $a->{err}\n"; # should print 1
> doent't work as I expected.
>
> Is there something wrong with the way I `bless`-ed the class Package2 ?
>
>
>
> ------>8-----------
> #! /usr/bin/perl


use strict;
use warnings;
# Modify your program to work with these.

> package Package1 ;
> sub new {
> my ($class) = @_;
> $class = __PACKAGE__ ;
>
> print "Call new [".$class."]\n" ;
> $r = [];
> $this = {};
> $class->{err} = 0 ;
>
> $r->[0] = Package2::new($this->{sock_fd} );
>
> bless $this, $class;
> return $this ;
> }
>
> sub IncErr {
> my $this = shift ;
> $this->{err} += 1 ;
> }
>
> sub Package2 {
> my $this = shift ;
> @_ ? ($r->[0] = shift) : $r->[0];
> }
>
>
>
>
>
>
> package Package2 ;
> @ISA = ("Package1");
> sub new {
> my ( $this, $sock ) = @_ ;
> $class = __PACKAGE__ unless @_;
> $this->{sock_fd} = $sock ;
> bless $this;
> return $this ;
> }
>
> sub Create {
> my $this = shift;
> print "Call Create\n" ;
> print "ERR2: $this->{err} ( this should print 2 )\n" ;
> $this->IncErr() ;
> print "ERR2: $this->{err} ( this should print 3 )\n" ;
> }
>
> my $a = Package1::new();
> $a->IncErr();
> $a->IncErr();
> $a->Package2->Create();
> print "ERR1: $a->{err} ( this should print 3 )\n" ;
>
> ------------------->8--------------------
>


I didn't look in detail at your program, but I also see that you use a
suboptimal syntax for creating objects; don't use "::"; use "->", e.g.

my $a = Package1->new();

Using "::" will work, but it creates problems that will make you
--such as preventing inheritance.

Using "use strict" and "use warnings" will help you catch errors like
the one above where you do "$a->Package2->Create()"


Rob Dixon

2006-11-03, 7:57 am

Mumia W. wrote:
>
> On 11/03/2006 02:23 AM, Muttley Meen wrote:
>
> This is not allowed because "Package2" is not a method within the class
> Package1. You want this:


Yes it is! It's an accessor method (albeit written wrongly). This is indeed a
very tangled web!

> my $b = Package2->new();
> $b->Create();
> print "ERR(\$b): $b->{err}\n";
>
>
> use strict;
> use warnings;
> # Modify your program to work with these.


Amen.

>
> I didn't look in detail at your program, but I also see that you use a
> suboptimal syntax for creating objects; don't use "::"; use "->", e.g.
>
> my $a = Package1->new();
>
> Using "::" will work, but it creates problems that will make you
> --such as preventing inheritance.


Is worse than suboptimal - it's wrong! Package1::new() won't pass the package
name as the first parameter. You could write the constructor differently of
course so that it fixes the call, but that's not how it's supposed to work.

> Using "use strict" and "use warnings" will help you catch errors like
> the one above where you do "$a->Package2->Create()"


Not true, but still an essential technique.

Rob
Rob Dixon

2006-11-03, 7:57 am

Muttley Meen wrote:
>
> Hi!
> Sorry if I double post, but the maillserver seems to have something against
> perl scripts attachements, so my first email was droped by the MTA : /
> Maybe if I put the code inline will be ok
>
> In the attached script I implement two packages:
> Package1 and Package2.
>
> Package2 is derived from Package1, which I guess I dont well.
> Now Package1 has a method called IncErr which increments a variable
> named $err.
>
> If I call something like:
>
> $a = Package1::new();
> $a->IncErr();
> print "ERR1: $a->{err}\n"; # should print 1
>
> all goes well, but if I call something like:
>
> $a = Package1::new();
> $a->Package2->Create(); # this should call IncErr too
> print "ERR1: $a->{err}\n"; # should print 1
> doent't work as I expected.
>
> Is there something wrong with the way I `bless`-ed the class Package2 ?


Your thinking is unclear in a number of areas, and you've made the classic
mistake of writing far too much before you start testing. It's also clearer and
easier for you to put your class definitions into modules and 'use' them into
the main program; it will compartmentalize your program and protect you from
making some simple mistakes. It is also the usual way in which classes are
implemented in Perl.

I've had trouble understanding your thinking in some areas, but here are some
comments which I hope will help you on your way.

> ------>8-----------
> #! /usr/bin/perl


Always, always, always

use strict;
use warnings;

especially if you're on unfamiliar territory and especially if you have bugs
that you can't fix. It's free, computerized debugging!

> package Package1 ;
> sub new {
> my ($class) = @_;
> $class = __PACKAGE__ ;


You've clearly found that the package name isn't in @_ as you expected so you've
put it in explicitly. Bad idea. If something isn't as you expect then find out
why and fix it. It may be that your expectations are wrong, but just as often,
as in this case, something is at fault somewhere else. You are calling the
package method new() as

$a = Package1::new();

which isn't a method call at all it's just a subroutine call, so Perl won't do
its magic of adding the package name as an implicit parameter. Use

$a = Package1->new;

and you won't have to 'fix' what Perl passes to you.

> print "Call new [".$class."]\n" ;
> $r = [];


What's $r, and what's wrong with using @r? I'm afraid I don't understand what
you're trying to do here, and there's some nasty stuff going on later on
involving $r too. Don't forget you're writing a class here, not main code.

> $this = {};
> $class->{err} = 0 ;


Perl has allowed you to do this as you haven't used 'strict', but it's a very
bad idea and clearly doesn't do what you think it does. $class holds the name of
the current package, 'Package1', and you're now using it as a symbolic reference
to a hash, so you're manipulating a package hash element, and this line is
equivalent to

$Package1::Package1{err} = 0;

What you mean is

$this->{err} = 0;

and 'use warnings' and 'use strict' would have helped you find this on your own.

> $r->[0] = Package2::new($this->{sock_fd} );


Hmm. Package2 depends on Package1 and also vice-versa. We could have a lot of
fun here! Remember that you're manipulating package variable $Package1::r, which
almost certainly isn't what you want.

> bless $this, $class;
> return $this ;
> }


Fine. Your blessing is correct, despite your misgivings!

> sub IncErr {
> my $this = shift ;
> $this->{err} += 1 ;
> }


Also correct.

> sub Package2 {
> my $this = shift ;
> @_ ? ($r->[0] = shift) : $r->[0];
> }


Spooky! Package1 not only builds an array of Package2 objects but also has a
method called Package2! I want to stop you doing this, but doesn't the fact that
you're assigning to $this and never using the value give you a clue? An object
method should normally manipulate only object data, so you need an element of
%$this to provide your $r thing. But, like I said, don't do that anyway!

> package Package2 ;
>
> @ISA = ("Package1");


Correct again.

> sub new {
> my ( $this, $sock ) = @_ ;


my ($class, $sock) = @_;

> $class = __PACKAGE__ unless @_;


Same thing as before. Two wrongs don't make a right, especially here where you'r
expecting an explicit parameter to the method ($sock). Defaulting the class name
won't happen here unless no parameters at all are passed in.

> $this->{sock_fd} = $sock ;
> bless $this;


Presumably you left the package name off the bless because you found it wasn't
being set up properly? bless() will bless the reference into the current package
unless you say otherwise, which in this case is what you want.

> return $this ;
> }
>
> sub Create {
> my $this = shift;
> print "Call Create\n" ;
> print "ERR2: $this->{err} ( this should print 2 )\n" ;
> $this->IncErr() ;
> print "ERR2: $this->{err} ( this should print 3 )\n" ;
> }


You should write an accessor method in Package1 to retrieve the value of the
error field, rather than using the hash element directly:

sub err {
my $this = shift;
$this->{err};
}

and then

my $err = $this->err;
print "ERR2: $err ( this should print 2 )\n";

or the ugly

print "ERR2: @{[$this->err]} ( this should print 2 )\n";

whcih avoids the temporary variable.




You also need

package main;

here, but better still put the class definitions in separate Perl module files
as I said before.

> my $a = Package1::new();


Why use 'my' when you have no strict pragma?

> $a->IncErr();
> $a->IncErr();
> $a->Package2->Create();


Perhaps the light is dawning. I think you ment to subclass Package2 in Package1
instead of what you've done. But Package1 isn't essentially a subclass of
Package2 any more than a zoo is a type of animal - it's just a class that
contains objects of another class.

> print "ERR1: $a->{err} ( this should print 3 )\n" ;
>
> ------------------->8--------------------



The reason this doesn't work as you expect is that you have three separate
error counts:

- In Package1::new you are initialising $Package1::Package1{err} to 0

- In the main code you are calling $a->IncErr (the only real object method
call around here) which first autovivifies $a->{err} (since the constructor
didn't create it) and then adds one to it. There are two calls so the end
result is two, which is printed in your last line of code as

ERR1: 2 ( this should print 3 )

- In package2::Create you are working on the object returned by $a->Package2,
which is $Package1::r->[0]. So you are printing $Package1::r->[0]->{err}
(which is undefined at first since, again, the constructor didn't create it)
and calling $Package1::r->[0]->IncErr() which creates it and gives it a value
of one. These values are printed from the Package2::Create method as

ERR2: ( this should print 2 )
ERR2: 1 ( this should print 3 )

I'm not sure where to send you from here, as you've dug yourself such a deep
hole! Perhaps start by using 'strict' and 'warnings' as I said and get your
program to clear those hurdles before you try to get your classes working
properly. Perhaps others can suggest a direction?

I hope this helps, and good luck!

Rob





Muttley Meen

2006-11-03, 6:57 pm

Well , here is what I come up with:

#! /usr/bin/perl
use strict;
use warnings;

package Package1 ;
sub new {
my ($class) = @_ ;

print "Call new [".$class."]\n" ;
my @r = [];
my $this = {};
$this->{err} = 0 ;

$this->{r}->[0] = Package2->new();
$this->{r}->[0]->{err} = $this->{err} ;

bless $this, $class ;
return $this ;
}


sub IncErr {
print "[IncErr]\n";
my $this = shift ;
$this->{err} += 1 ;
}

#accessor method for Package2
sub Package2 {
my $this = shift ;
print "[Package2]\n" ;
@_ ? ($this->{r}->[0] = shift) : $this->{r}->[0];
}


#accessor method for err
sub err {
my $this = shift;
$this->{err};
}




package Package2 ;
our @ISA = ("Package1");
sub new {
my ( $class, $sock ) = @_ ;
my $this = {} ;
bless $this, $class;
return $this ;
}

sub Create {
my $this = shift;
print "[Package2->Create]\n" ;
print "CREATE:\t@{[$this->err]} \t\t( this should print 2 )\n" ;
$this->IncErr() ;
print "CREATE:\t@{[$this->err]} \t\t( this should print 3 )\n" ;
}

$a = Package1->new();
$a->IncErr();
$a->IncErr();
print "ERR1:\t$a->{err} \t\t( this should print 2 )\n" ;
$a->Package2->Create();
print "ERR1:\t$a->{err} \t\t( this should print 3 )\n" ;



On 11/3/06, Rob Dixon <rob.dixon@350.com> wrote:
> Mumia W. wrote:
>
> Yes it is! It's an accessor method (albeit written wrongly). This is indeed
> a
> very tangled web!
>
>
> Amen.
>
>
> Is worse than suboptimal - it's wrong! Package1::new() won't pass the
> package
> name as the first parameter. You could write the constructor differently of
> course so that it fixes the call, but that's not how it's supposed to work.
>
>
> Not true, but still an essential technique.
>
> Rob
>
> --
> To unsubscribe, e-mail: beginners-unsubscribe@perl.org
> For additional commands, e-mail: beginners-help@perl.org
> <http://learn.perl.org/> <http://learn.perl.org/first-response>
>
>
>

Mumia W.

2006-11-03, 6:57 pm

On 11/03/2006 04:57 AM, Rob Dixon wrote:
> Mumia W. wrote:
>
> Yes it is! It's an accessor method (albeit written wrongly). This is
> indeed a
> very tangled web!
>


Yeah, I should've looked more closely at Muttley's program.


>
> Amen.
>
>
> Is worse than suboptimal - it's wrong! Package1::new() won't pass the
> package
> name as the first parameter. You could write the constructor differently of
> course so that it fixes the call, but that's not how it's supposed to work.
>
>
> Not true, but still an essential technique.
>
> Rob
>


And I should've tested that. :-(


Mumia W.

2006-11-03, 6:57 pm

On 11/03/2006 10:16 AM, Muttley Meen wrote:
> On 11/3/06, Rob Dixon <rob.dixon@350.com> wrote:
>
> Well , here is what I come up with:
>
> #! /usr/bin/perl
> use strict;
> use warnings;
>
> package Package1 ;
> sub new {
> my ($class) = @_ ;
>
> print "Call new [".$class."]\n" ;
> my @r = [];
> my $this = {};
> $this->{err} = 0 ;
>
> $this->{r}->[0] = Package2->new();
> $this->{r}->[0]->{err} = $this->{err} ;
>
> bless $this, $class ;
> return $this ;
> }
>
>
> sub IncErr {
> print "[IncErr]\n";
> my $this = shift ;
> $this->{err} += 1 ;
> }
>
> #accessor method for Package2
> sub Package2 {
> my $this = shift ;
> print "[Package2]\n" ;
> @_ ? ($this->{r}->[0] = shift) : $this->{r}->[0];
> }
>
>
> #accessor method for err
> sub err {
> my $this = shift;
> $this->{err};
> }
>
>
>
>
> package Package2 ;
> our @ISA = ("Package1");
> sub new {
> my ( $class, $sock ) = @_ ;
> my $this = {} ;
> bless $this, $class;
> return $this ;
> }
>
> sub Create {
> my $this = shift;
> print "[Package2->Create]\n" ;
> print "CREATE:\t@{[$this->err]} \t\t( this should print 2 )\n" ;
> $this->IncErr() ;
> print "CREATE:\t@{[$this->err]} \t\t( this should print 3 )\n" ;
> }
>
> $a = Package1->new();
> $a->IncErr();
> $a->IncErr();
> print "ERR1:\t$a->{err} \t\t( this should print 2 )\n" ;
> $a->Package2->Create();
> print "ERR1:\t$a->{err} \t\t( this should print 3 )\n" ;
>
>
>


Please bottom-post.

Why should a call to Create() on an object returned by Package2()
updatethe object $a ?

It shouldn't normally update the containing object, and it doesn't in
your program.

For your education, try this,
print "Inside ERR:\t", $a->Package2->err, "\n";

Also, typically one uses either containment or inheritance but not both.

Please explain what you're trying to do again.




Muttley Meen

2006-11-04, 7:57 am

On 11/3/06, Mumia W. <mumia.w.18.spam+nospam@earthlink.net> wrote:
>
> Please bottom-post.
>
> Why should a call to Create() on an object returned by Package2()
> updatethe object $a ?
>
> It shouldn't normally update the containing object, and it doesn't in
> your program.
>
> For your education, try this,
> print "Inside ERR:\t", $a->Package2->err, "\n";
>
> Also, typically one uses either containment or inheritance but not both.
>
> Please explain what you're trying to do again.


The ideea is to implement a context based protocol, though you can't
see that from the script.
Let's say that you have several contexts, like
Context1
->Context2/OP
->Context2.1/OP
->Context3/OP
Given this, I wanted to reflect the context structure into the code written by
the module user, something like
Context1->Context2->OP ( Where OP is any givent operation).
Now, the operations might trigger protocol errors, which are
held(counted) in the parent class( Context1 ), and thus should be
modifiable by any child method( in this example,
by Context2/OP, Context2.1/OP, Context3/OP).

getting back the the example script, Package1 would be the base class, meaning
in the same time the top context.
Package2, would be in the same time a method of Package1, in order to
call it like
Package1->Package2->OP, and it would be a package of itself too.

The IncErr method simply (would)increment the number of errors reported
by the protocol.
Mumia W.

2006-11-04, 6:56 pm

On 11/04/2006 06:05 AM, Muttley Meen wrote:
> On 11/3/06, Mumia W. <mumia.w.18.spam+nospam@earthlink.net> wrote:

What I meant was, "in a single class (A), usually a programmer will
either inherit from a base class (B) or contain an object of that class
(B), but not both inherit from (B) and contain (B)."

[color=darkred]
>
> The ideea is to implement a context based protocol, though you can't
> see that from the script.
> Let's say that you have several contexts, like
> Context1
> ->Context2/OP
> ->Context2.1/OP
> ->Context3/OP
> Given this, I wanted to reflect the context structure into the code
> written by
> the module user, something like
> Context1->Context2->OP ( Where OP is any givent operation).
> Now, the operations might trigger protocol errors, which are
> held(counted) in the parent class( Context1 ), and thus should be
> modifiable by any child method( in this example,
> by Context2/OP, Context2.1/OP, Context3/OP).
>
> getting back the the example script, Package1 would be the base class,
> meaning
> in the same time the top context.
> Package2, would be in the same time a method of Package1, in order to
> call it like
> Package1->Package2->OP, and it would be a package of itself too.
>
> The IncErr method simply (would)increment the number of errors reported
> by the protocol.
>


I suggest you let all of the contexts use the same base class and let
all of the objects share the same error count variable (using
references), e.g.

#!/usr/bin/perl
use strict;
use warnings;
use Class::Struct;
use Data::Dumper;

struct ContextBase => { errRef => '$', context => '$' };

package ContextBase;

sub addContext {
no strict 'refs';
my $self = shift;
my $context = shift; # context package name

# Check the context package name.
die "Bad context" unless ($context =~ /^Context\w/);
my $newctx = $context->new;

# This object now contains a new context object.
$self->context($newctx);

# The new context object should know about the error
# count variable.
$newctx->errRef($self->errRef);

# Create a method for accessing the sub-context.
*{$context} = sub {
my $self = shift;
$self->context(shift()) if (@_);
$self->context;
};
$newctx;
}

sub err {
${shift()->errRef};
}

sub incErr {
++${shift()->errRef}
}

package Context1;
our @ISA = qw(ContextBase);

package Context2;
our @ISA = qw(ContextBase);

package Context3;
our @ISA = qw(ContextBase);

######## Main program ###########
package main;

my $errCount = 0;
my $obj = Context1->new(errRef => \$errCount);
$obj->addContext('Context2')->addContext('Context3');
print Dumper(\$obj);

print '----------------------', "\n";
$obj->incErr;
print 'Error Count: ', $obj->err(), "\n";
$obj->Context2->incErr;
print 'Error Count: ', $obj->err(), "\n";
$obj->Context2->Context3->incErr;
print 'Error Count: ', $obj->err(), "\n";

__END__


If you don't have the Class::Struct module, get it. It simplifies
creating classes that have many accessor methods. Also get
Class::Accessor and Class::Data::Inheritable; they do the same thing as
Class::Struct but are more powerful.


HTH



JupiterHost.Net

2006-11-06, 9:56 pm



Muttley Meen wrote:
> Hi!


Hello,

> $a = Package1::new();
> $a->Package2->Create(); # this should call IncErr too
> print "ERR1: $a->{err}\n"; # should print 1
> doent't work as I expected.
>
> Is there something wrong with the way I `bless`-ed the class Package2 ?


use strict, warnings, and most important: use base

And don;t use $a or $b as variable names, oi :)

Damian Conway's "Perl Best Practice" will make your life much easier!
Muttley Meen

2006-11-06, 9:56 pm

On 11/6/06, JupiterHost.Net <mlists@jupiterhost.net> wrote:
>
>
> Muttley Meen wrote:
>
> Hello,
>
>
> use strict, warnings, and most important: use base
>
> And don;t use $a or $b as variable names, oi :)
>
> Damian Conway's "Perl Best Practice" will make your life much easier!
>

I thought that $a might've been the cause :P

Anyway, I come to see my flaw, which was the misconception of inheritance.
I thought if B is inherited from A, and A was instantiated, then the Bs members
will refer to the same memory locations as As members, which is wrong.
I could deal with this by passing a parameter to the child constructor, pointing
to the parent $this reference.

Mumia, use Class::Struct uses the same trick of defining an array of methods,
similar to my $this->{r} used in the attached test.pl. Actually Class::Struct
generates perl code in a variable, and then evaluates it.
Sponsored Links







Also available: Server administration forum archive | Web Design forum archive | Software forum archive | Hardware reviews archive

Copyright 2009 codecomments.com