Abigail said:
Anno Siegel (
[email protected]) wrote on MMMMCCCXLI
September MCMXCIII in said:
[[ Snipped for brevity ]]
Here's how I would subclass Angry::Snake. Note that I subclass the
original Angry::Snake, without requiring it to have an "accessor".
Ah, but in my book (that would be a little paper I'm writing about
inheritance in Perl) it *has* an accessor. Any method that accesses the
object by de-referencing the object is an accessor, whether it returns
the value to the caller or does something else with it. That would make
->poke_it_with_a_stick an accessor. A field accessor in the usual
sense is just a special case. In most mainstream OO languages there
are no accessors except field accessors, so the two notions coincide,
but Perl is different.
That's just terminology. Maybe I should think it over and find a better
term. But what can you call a method whose distinguishing property is that
it accesses the object?
package Angry::Snake;
sub new {
bless \do {my $c = int rand 5} => shift;
}
sub poke_it_with_a_stick {
my $snake = shift;
$snake -> attack if $$snake -- < 0;
}
sub attack { ... }
package Sleepy::Snake; {
use Scalar::Util qw 'refaddr';
our @ISA = qw /Angry::Snake/;
my %sleepiness; # Store the attribute in a lexical variable.
sub set_sleepiness {
my $snake = shift;
$sleepiness {refaddr $snake} = shift;
$snake;
}
sub sleepiness {
my $snake = shift;
$sleepiness {refaddr $snake};
}
sub poke_it_with_a_stick {
my $snake = shift;
$snake -> SUPER:
oke_it_with_a_stick
if rand > $snake -> sleepiness;
}
sub DESTROY {
my $snake = shift;
delete $sleepiness {refaddr $snake};
}
}
package main;
my $snake = Sleepy::Snake -> new -> set_sleepiness (0.5);
Note that Sleepy::Snake doesn't have its own constructor.
No, it can't. One of the tenets about Perl inheritance is "If you
inherit an accessor, you must also inherit ->new", which is what happens
here. Inherit as opposed to override.
Like all short formulas about complex fields (except exp(i*pi) = -1, hehe),
it is wrong. You can override ->new, provided you call the base class'
->new to create the object. Your objects must be structurally the same
as those of the base class if you want the base class accessors to work
on them. Put like that it's a truism.
However, in package Sleepy::Snake it would be safe to override ->new
sub new { shift()->SUPER::new->set_sleepiness( 0.5) }
so as not to create snakes with undefined sleepiness.
Nor does it rely
on how Angry::Snake has been implemented in any way. It doesn't require
its super class to make accessors available. It doesn't force anything
on a potential subclass either.
Quite so, it carries itself the full weight of inheriting from the
deliberately inheritance-unfriendly Angry::Snake. The weight is the
somewhat unusual implementation. It shows again that inheritance in
Perl doesn't come for free. Someone must support it, the base class
and/or the inheriting class.
So "Inside-out" is what you call them. (I've read some more of the thread,
but wanted to reply with your code in sight.) It's an ingenious answer
to the question: How can I assign additional data (fields) to an object when
I'm not allowed to change its structure?
I suppose you are using Scalar::Util::refaddr because stringification
may be overloaded for the base class. Otherwise the stringified object
would serve as well. I have used overload::StrVal for the purpose.
On re-reading perldoc overload I see that there is now a pointer to
Scalar::Util::refaddr.
Just out of spite, below is yet another way to inherit poke_it_with-
_a_stick from an (unchanged) Angry::Snake. It uses de-reference
overloading to persuade the base class to access Sleepy::Snake objects
in a special way. Overloading every possible kind of de-reference makes
sure it continues to work when Angry::Snake changes its implementation.
It is more heavy handed than the elegant inside-out objects, and it inhibits
further inheritance in a major way. Overloading de-reference is bad for
inheritance, carpet-bombing it over all types, as I do here, is worse.
The merit of this way, if any, is the standard implementation of
Sleepy::Snake as a typical hash-as-a-struct.
package Sleepy::Snake;
use base 'Angry::Snake';
use overload map { $_ => 'snake' } qw( ${} @{} %{} &{} *{});
sub new {
my $class = shift;
bless {
sleepiness => 0.5,
snake => Angry::Snake->SUPER::new,
}, $class;
}
sub set_sleepiness { $_[ 0]->{ sleepiness} = $_[ 1]; shift }
sub sleepiness { $_[ 0]->{ sleepiness} }
# we're the ->{ snake} field for Angry::Snake, ourselves for
# everyone else
sub snake { caller eq 'Angry::Snake' ? $_[ 0]->{ snake} : $_[ 0] }
sub poke_it_with_a_stick {
my $snake = shift;
$snake -> SUPER:
oke_it_with_a_stick if rand > $snake -> sleepiness;
}
__END__
Anno