Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

subclassing associated classes elegantly

0 views
Skip to first unread message

Darren Duncan

unread,
Oct 19, 2005, 6:11:21 PM10/19/05
to perl6-l...@perl.org, dar...@darrenduncan.net
I'm in a long-standing situation with my module development where I
want to design a set of associated classes such that invocations of
submethods or class methods, such as new(), of one class by another
class continue to work as expected when any or all of those classes
is subclassed. I have a solution already that works, but I'm looking
for the most optimal solution.

An example of when this situation can arise is if person X implements
a simplified XML DOM implementation using 2 classes, Document and
Node, that work together, where one of those classes (Document) can
create objects of the other (Node), and person Y wants to subclass
the XML DOM implementation, meaning that those same Node objects made
by one of person Y's Document subclass should be objects of person
Y's Node subclass.

To illustrate, say we had these 4 classes (the syntax may be wrong):

# This is one associated class set:

class A {
submethod one () {
return 'hello';
}

submethod two () {
B.four();
}
}

class B {
submethod three () {
A.one();
}

submethod four () {
return 'here';
}
}

# This is separate and optional associated class set:

class C is A {
submethod one () {
return 'world';
}
}

class D is B {
submethod four () {
return 'there';
}
}

What I want to be able to do is set things up so that user code can
do something that is effectively like this:

my $first = A.two(); # returns 'here'
my $second = B.three(); # returns 'hello'
my $first = C.two(); # returns 'there'
my $second = D.three(); # returns 'world'

The situation is that classes C and D represent any arbitrary named 2
classes that are subclassed from A and B, and so the latter can't
know the names of the former, and the latter have to work
independently of C and D also.

This is one variant of a solution I have come up with:

# This is one associated class set:

role AB {
submethod name_of_class_A () {
return 'A';
}

submethod name_of_class_B () {
return 'B';
}
}

class A does AB {
submethod one () {
return 'hello';
}

submethod two () {
.name_of_class_B().four();
}
}

class B does AB {
submethod three () {
.name_of_class_A().one();
}

submethod four () {
return 'here';
}
}

# This is separate and optional associated class set:

role CD {
submethod name_of_class_A () {
return 'C';
}

submethod name_of_class_B () {
return 'D';
}
}

class C is A does CD {
submethod one () {
return 'world';
}
}

class D is B does CD {
submethod four () {
return 'there';
}
}

This is another variant of a solution I have come up with:

# This is one associated class set:

role AB {
submethod invoke_one () {
return A.one();
}

submethod invoke_four () {
return B.four();
}
}

class A does AB {
submethod one () {
return 'hello';
}

submethod two () {
.invoke_four();
}
}

class B does AB {
submethod three () {
.invoke_one();
}

submethod four () {
return 'here';
}
}

# This is separate and optional associated class set:

role CD {
submethod invoke_one () {
return C.one();
}

submethod invoke_four () {
return D.four();
}
}

class C is A does CD {
submethod one () {
return 'world';
}
}

class D is B does CD {
submethod four () {
return 'there';
}
}

In either case, the expectation here is that the submethods of role
CD will override those of role BC regardless of which class' other
methods invoke those, when the invocant class is C or D.

So I'm wondering what is the best way to develop my associated class
sets such that it is easiest for third parties to be able to
subclass-extend them. Should I use one of the two solutions above
(both of which have been tried in real life, in Perl 5, the second
more recently)? Or is there another solution that is better than
both?

Also, in such a situation as the above, is it reasonable to support
easy subclassing, or would it be better to avoid that complexity and
instead expect users to create objects that wrap the others instead
of subclassing them?

Assume also that it may be counter-productive for one class to expect
user code to invoke the second class on its behalf, such as if when
pair of classes is hidden behind a second pair of classes that
mediate access to them.

What are some best practices here that can be used by anyone faced by
a similar problem?

-- Darren Duncan

Larry Wall

unread,
Oct 19, 2005, 6:42:00 PM10/19/05
to perl6-l...@perl.org
On Wed, Oct 19, 2005 at 03:11:21PM -0700, Darren Duncan wrote:
: What are some best practices here that can be used by anyone faced by
: a similar problem?

My battery's running low, so I just skimmed your article, but
my impression is that this is something that would be handled by
virtualizing all class names within methods to be the version of
the class seen by the actual object rather than the declared class.
This is a behavior that has already been mandated, not just for inner
classes, but any external class referenced by name. (This applies
only within methods. Within a class outside the methods, names
are not virtual.)

Larry

Stevan Little

unread,
Oct 19, 2005, 7:31:04 PM10/19/05
to Darren Duncan, perl6-l...@perl.org
Darren,

Your problem reminds me of the "Expression Problem", which is
something that IIRC Luke's Theory idea was trying to solve. Here is
the link to a paper Luke referred me to on the subject:

http://scala.epfl.ch/docu/files/IC_TECH_REPORT_200433.pdf

Also, you can Google the phrase "Expression Problem" and find quite a
bit on the subject.

Stevan

Rob Kinyon

unread,
Oct 19, 2005, 8:55:04 PM10/19/05
to Darren Duncan, perl6-l...@perl.org
On 10/19/05, Darren Duncan <dar...@darrenduncan.net> wrote:
[snip]
[snip]

In Perl5, I would think the easiest solution would be to "trick" the
base Document class into using the right Node class.

1) Load Node.
2) Rename Node to Node::Base
3) Create your Node, subclassing Node::Base
4) Load Document
5) Rename Document to Document::Base
6) Create your Document, subclassing Document::Base

Something along the lines of:

package Node::Mine;

use Node;

*Node::Base = *Node;
delete $main::{Node};

package Node;

use base 'Node::Base';

etc.

So, I figure that with packages being a lot simpler to work with in
P6, that should be a lot easier to handle. Especially given that
packages and modules are now first-class.

Rob

Luke Palmer

unread,
Oct 19, 2005, 11:48:41 PM10/19/05
to Stevan Little, Darren Duncan, perl6-l...@perl.org
On 10/19/05, Stevan Little <ste...@iinteractive.com> wrote:
> Darren,
>
> Your problem reminds me of the "Expression Problem", which is
> something that IIRC Luke's Theory idea was trying to solve.

Indeed, this problem is almost exactly the contravariant half of the
expression problem. Once upon a time, a theory was supposed to
represent your "role AB", but it has diverged from that. Nowadays I
think a problem like this solved by deriving from a module, which
isn't too unlike what you were showing. The "factory" abstraction
from the theory proposal solves the well-typedness of this kind of
solution.

Here's a guess:

module AB;

class A {
submethod one () {
return 'hello';
}

submethod two () {
B.four();
}
}

class B {
submethod three () {
A.one();
}

submethod four () {
return 'here';
}
}

module CD;
is AB;

class A is AB::A {


submethod one () {
return 'world';
}
}

class B is AB::B {


submethod four () {
return 'there';
}
}

This is treating all names in a module as virtual. There may be a
downside to this, in which case we could go Scala's direction and make
the distinguishing property of a role (there called "trait") that the
things it declares are virtual.

However, if you're ever deriving from a module, a somewhat uncommon
thing to do, supposedly you're doing so precisely to make the names
virtual like this. So this may just be the right solution.

Luke

0 new messages