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

supertyping

7 views
Skip to first unread message

TSa

unread,
Dec 11, 2006, 12:33:16 PM12/11/06
to p6l
HaloO,

I've mentioned the concept of supertyping in several posts
but now I want to dedicate a thread to it because it increases
the expressive power of Perl 6 if one can form abstractions
not only by creating subclasses and subroles but also by going
in the other direction. Here are some use cases where this is
useful:

Set <: Bag
Set <: FuzzySet

Num <: Complex <: Quaternion <: Octonion

Vector <: Matrix <: Tensor

Square <: Rectangle

The subtypes should be considered as given and the supertypes as
derived. Some code follows that explains how that might be done
for the Num <: Complex case. The key idea is that the ComplexRole
provides a default implementation of the .im accessor for Num.

role NumRole does Order
{
multi infix:<+> ( Num $a, Num $b) {...}
multi infix:<*> ( Num $a, Num $b) {...}
method abs {...}
...
}

class Num does NumRole {...}

role ComplexRole superdoes Num # handwave, note that Complex doesn't

# do Order which is fine for a supertype
{
method re { return self as Num } # noop for Num
method im { return 0 }
method abs {...}
method arg { return self.re < 0 ?? pi !! 0 }
multi infix:<+> ( Num $a, Num $b) {...}
multi infix:<*> ( Num $a, Num $b) {...}
...
}

class Complex does ComplexRole
{
# accessors overwrite role defaults
has Num $.re;
has Num $.im;
multi infix:<+> ( Complex $a, Complex $b)
{
return new Complex( $a.re + $b.re, $a.im + $b.im );
}
multi infix:<*> ( Complex $a, Complex $b)
{
return new Complex( $a.re * $b.re - $a.im * $b.im,
$a.re * $b.im + $a.im * $b.re );
}
method abs { return sqrt( self.re**2 + self.im**2 ); }
method arg
{
if self.im > 0 { return acos( self.re / self.abs ); }
elsif self.im < 0 { return 2*pi - acos( self.re / self.abs ); }
elsif self.re < 0 { return pi; }
else { return 0; }
}
...
}

This works as far as the Complex numbers are defined in terms of the
.re and .im methods but fails as soon as you want them to be Complex
constructors in lvalue context:

use Complex;
my $x = 3.2;
$x.im = 1.2; # becomes a non-real complex
$x.arg = pi/4; # same

The question here is how the class Complex can receive the dispatch on
the Num class for putting a new Complex into $x. This is BTW yet another
example of changing an objects type while preserving its identity---see
the thread 'how to change the type of objects'. I mean here we have
value semantics anyway but in general a superclass might want to
preserve object identity while changing the type.

So will we have supertyping in Perl 6? If yes, how would my superdoes
be spelled? Same applies to a superis for classes.


Regards, TSa.
--

Jonathan Lang

unread,
Dec 11, 2006, 2:15:04 PM12/11/06
to p6l
For those of us who don't think in terms compatible with Type Theory,
the suggestion is essentially this: instead of going from general to
specific, go from specific to general. E.g., for a given relationship
::A.does(::B) or ::A.isa(::B), include a way to start with ::B and
then to define ::A in terms of it.

Some thoughts:

Perl 6 currently defines four approaches to code reuse:

inheritance from a class ("...is class")
composition of a role ("...does role")
delegation to an attribute ("attribute handles methods")
subtypes ("...where condition")

The first three approaches share the common approach of adding to what
the object can do; the fourth approach adds restrictions on the values
that the object may have. Reverse-engineering these would involve
removing capabilities (in the first three cases) or removing
restrictions (in the fourth case). In any case, one should never add
anything while going from specific to general.

--

Looking at the inheritance/composition/delegation triad for a moment:

I suppose that a case could be made to that subtyping could be used to
remove capabilities from an object (e.g., "... where !.can(&method)"
to remove one method from it); but doing so is syntactically clumsy
and semantically suspect (if "::B.can(&foo)" and "::B where
!.can(&foo)" is equivalent to ".does(::B) and !.can(&foo)", then the
latter ought always be false).

I don't know how (or even if) I'd handle reverse-inheritance or
reverse-delegation; but for reverse-composition (or decomposition, if
you will), I'd handle it either by using set operations to define a
subset of the role's methods to decompose into a more general role (as
I've talked about earlier), or I'd define the more general role in
terms of what methods it _doesn't_ have:

role B {
method foo() { ... }
method bar() { ... }
}
my subset A of B except <foo>

(where the except clause tells you which methods to remove when
constructing A) would produce the same end result as:

role A {
method bar() { ... }
}
role B does A {
method foo() { ... }
}

Personally, I feel that the set operations approach is the more
flexible and user-friendly one, and it makes the one outlined here
redundant. The one capability that both approaches miss is the
ability to have the more general class redefine the behavior of
existing methods.

--

In terms of the subtypes, the reverse process would be to somehow
remove restrictions from the object. This could (conceptually) be
done in one of two ways: granting exemptions to the restrictions (in
effect, adding conditions that, if met, permit the value despite
existing restrictions), or specifying existing restrictions to be
removed. I'm not sure how (or if) I'd implement the latter.

In effect, an exemption clause would follow the rule of "if you pass
this test, you're OK; if not, we'll pass you up the chain for further
scrutiny" - as opposed to where clauses, which say "if you fail this
test, you're out; if not, we'll pass you up the chain for further
scrutiny". That is, "where" clauses should tack a new condition onto
the start of the overall test by means of a short-circuiting "and";
exemptions would tack a new condition onto the start by means of a
short-circuiting "or". Since the "default" test (prior to the first
restriction or exemption) is "true", this would make "bare" exemptions
useless.

That said, I'm not sure what keyword would be the most user-friendly
for defining exemptions.

--
Jonathan "Dataweaver" Lang

TSa

unread,
Dec 12, 2006, 3:41:55 AM12/12/06
to p6l
HaloO,

Jonathan Lang wrote:
> In any case, one should never add
> anything while going from specific to general.

The crux of my example is that one indeed adds methods
in the supertype. The subtype receives a standard
implementation. This pattern is applicable whenever
you have a case like the Num that can be embedded in
the Complex with .im == 0. Another example is deriving
a Point3D from a Point2D by giving a default
implementation of .z == 0.


Regards, TSa.
--

Luke Palmer

unread,
Dec 13, 2006, 1:51:43 AM12/13/06
to TSa, p6l

I'd tend to agree. This is an important feature of an object system
for me, if only because it allows the Num => Complex transformation,
which is one that has always bugged me (more specifically, if the
language gives you real numbers but not complex numbers, how do you
integrate complex numbers in a seamless way; i.e. such that the reals
are in fact a subset of the complexes).

There is a nice duality in this specification. When you say:

role Foo does Bar does Baz {...}

You are saying Foo = Bar (*) Baz (*) ...; # intersection of Bar, Baz,
and the following spec

When you say:

role Foo superdoes Bar superdoes Baz {...}

You are saying Foo = Bar (+) Baz (+) ...; # union of Bar, Baz, and
the following spec

It may seem like that should be Foo = (Bar (+) Baz) (*) ...; (indeed,
I thought that for a while and then corrected myself). But if
anything that is a Bar is a Foo, anything that is a Baz is a Foo, and
some other stuff is also a Foo (this being the important case), then
that should clearly be a union.

Things work a little differently for required methods. When a
superrole requires a method be implemented, we (the language
designers) have a choise to make: it is illegal if the superrole
requires a method that the subroles don't implement or don't
themselves require, or it simply adds the new method to the required
list of the subroles which don't implement it. I'm inclined to say
the former, even though it seems a little more brittle. It means that
when you are implementing a role in a class, all you have to look at
for what you need to do is the role and the roles that it includes;
you don't need to scan the entire source for superroles which
encompass the one you are implementing. That is the user-friendly way
to put it, but there is an analogous argument for independent
modularity (you don't want the definition of a new superrole to
invalidate objects that already exist).

Things get a little tricky when you think about "does" and "superdoes" together:

role Foo does Bar superdoes Baz {...}

What does that mean? Okay, Foo is a subset of Bar, and a superset of
Baz. Already by simply declaring this we have mandated that "Baz does
Bar". If Baz doesn't already conform to the interface of Bar, Foo
better fill in the gaps for it. As for the ..., anything which
conforms to it and Bar ought to be in Foo. So I think the equation
ends up:

Foo = Bar (*) (Baz (+) ...);

In words, you are a Foo if you are a Bar and you either are a Baz or
conform to my interface.

You can get into some yucky grouping issues because union and
intersection do not commute with each other, but I think the rule for
determining the equation that seems "natural" is the following: if
your declaration looks like "role Foo does B1 does B2 ... does Bn
superdoes D1 superdoes D2 ... superdoes Dm { SPEC }", then the
equation will be:

Foo = B1 (*) B2 (*) ... (*) Bn (*) (D1 (+) D2 (+) ... (+) Dm (+) SPEC)

That is, you have to do all the Bs, and you may either do any of the
Ds or do my interface. Keep in mind that all the Di will conform to
"my interface" after this declaration anyway.

Whew, I was really only going to reply with an "I dig it" sort of
response, but I seem to have written some sort of complete theory. :-)

Exercise for the reader: generalize to subtypes, delegation, and inheritance.

Luke

Luke Palmer

unread,
Dec 13, 2006, 1:55:16 AM12/13/06
to TSa, p6l
On 12/13/06, Luke Palmer <lrpa...@gmail.com> wrote:
> Things work a little differently for required methods. When a
> superrole requires a method be implemented, we (the language
> designers) have a choise to make: it is illegal if the superrole
> requires a method that the subroles don't implement or don't
> themselves require, or it simply adds the new method to the required
> list of the subroles which don't implement it.

Woah, that was a terrible passage. Let's try again. If your
declaration is "role Foo superdoes Bar".

* it is illegal to add a required method to Foo if Bar doesn't
already implement it or require it
* any required method for Foo automatically becomes a required
method for Bar, even if it wasn't there before

I think my case below is still understandable, if a little awkward.

> I'm inclined to say
> the former, even though it seems a little more brittle. It means that
> when you are implementing a role in a class, all you have to look at
> for what you need to do is the role and the roles that it includes;
> you don't need to scan the entire source for superroles which
> encompass the one you are implementing. That is the user-friendly way
> to put it, but there is an analogous argument for independent
> modularity (you don't want the definition of a new superrole to
> invalidate objects that already exist).

Luke

Jonathan Lang

unread,
Dec 13, 2006, 3:58:07 AM12/13/06
to p6l
Luke Palmer wrote:
> When you say:
>
> role Foo superdoes Bar superdoes Baz {...}
>
> You are saying Foo = Bar (+) Baz (+) ...; # union of Bar, Baz, and
> the following spec

For the record, I think that "superdoes" should be spelled "done_by".

> It may seem like that should be Foo = (Bar (+) Baz) (*) ...; (indeed,
> I thought that for a while and then corrected myself). But if
> anything that is a Bar is a Foo, anything that is a Baz is a Foo, and
> some other stuff is also a Foo (this being the important case), then
> that should clearly be a union.

Note that in your usage of the set operations, "Bar (+) Baz" requires
"Bar.^methods (*) Baz.^methods" and "Bar (*) Baz" requires
"Bar.^methods (+) Baz.^methods". This means that the spec that
follows would act to further limit the methods that end up in Foo,
such that only methods that appear in Bar, Baz, _and_ the spec would
end up in Foo. (As a secondary issue, your definition of "Bar (+)
Baz" strikes me as counterintuitive; but maybe that's because I tend
to view roles as sets of methods.)

> Things work a little differently for required methods. When a
> superrole requires a method be implemented, we (the language

> designers) have a choice to make: it is illegal if the superrole


> requires a method that the subroles don't implement or don't
> themselves require, or it simply adds the new method to the required
> list of the subroles which don't implement it. I'm inclined to say
> the former, even though it seems a little more brittle.

I agree with your inclination; but bear in mind that this would make
it illegal to define Complex as a superrole of Num, since Complex has
methods that Num doesn't have. Also bear in mind that if Complex is a
superrole of Num, a parameter that asks for a Num should by all rights
reject a Complex that's handed to it.

I agree with the idea of being able to define superroles (for lack of
a better name); my problems lie entirely with the proposed
implementation of the concept. I'll reiterate my alternative:

When using set operations with roles, treat the roles as sets of
methods. Each set operation defines an anonymous role with the
resulting set of methods; as with any other anonymous thing, it can be
bound to a name. In addition, a few of the set operations establish
.does() relationships between the new role and the roles used to
create it:

role C ::= A (+) B # C.does(A) and C.does(B)
role D::= A (*) B # A.does(D) and B.does(D)
role E ::= A (-) B # A.does(E)

and so on. In particular, these lines would be equivalent to

role C does A does B {}
role D done_by A done_by B {}
role E done_by A except B {}

...with the added benefit that you don't have any confusion as to how
"does", "done_by", "except", etc. interact with each other or with the
new spec. Further, grouping of set operations makes it easy to define
"staged composition":

(A (+) B) (*) C

would mean "compose an anonymous role as the union of A and B, and
then compose an anonymous role as the intersection of that and C".
Among other things, this would allow method definitions in C to
override those of the anonymous union.

--
Jonathan "Dataweaver" Lang

Smylers

unread,
Dec 13, 2006, 6:25:40 AM12/13/06
to perl6-l...@perl.org
Jonathan Lang writes:

> For the record, I think that "superdoes" should be spelled "done_by".

I think it's unlikely that Larry will incorporate any keywords that
contain underscores -- certainly not without at least searching for a
single word that sums up the concept in question.

Smylers

TSa

unread,
Dec 13, 2006, 7:01:43 AM12/13/06
to p6l
HaloO Luke,

good to hear from you again!

you wrote:
> I'd tend to agree. This is an important feature of an object system
> for me,

And I hope for the rest of @Larry, too.


> There is a nice duality in this specification. When you say:
>
> role Foo does Bar does Baz {...}
>
> You are saying Foo = Bar (*) Baz (*) ...; # intersection of Bar, Baz,
> and the following spec
>
> When you say:
>
> role Foo superdoes Bar superdoes Baz {...}
>
> You are saying Foo = Bar (+) Baz (+) ...; # union of Bar, Baz, and
> the following spec

First of all it strikes me as odd that you have union as (+) and
intersection as (*). Is that the official definition? I would like
to see (|) and (&) because of the duality to the logical ops. Besides
the fact that (+) could mean Bag addition and (*) the cartesian
product.

Secondly I figure you are operating on the extension set a role
defines. That is union produces a supertype aka the larger set
of instances. This is in accordance to the usual type theory
approach. Note that Jonathan operates on the intension set of roles
that is union produces a subtype.


> Things work a little differently for required methods. When a
> superrole requires a method be implemented, we (the language
> designers) have a choise to make: it is illegal if the superrole
> requires a method that the subroles don't implement or don't
> themselves require, or it simply adds the new method to the required
> list of the subroles which don't implement it. I'm inclined to say
> the former, even though it seems a little more brittle.

I can see no brittleness. The role that is supertyped is already
fully defined when the superrole is about to be created. Thus it can
be checked if required but unimplemented methods---that is ones with
a {...} body---are present. These should be rejected on the rational
that the pre-existing code can't retroactively be made to implement
the method.


> It means that
> when you are implementing a role in a class, all you have to look at
> for what you need to do is the role and the roles that it includes;
> you don't need to scan the entire source for superroles which
> encompass the one you are implementing. That is the user-friendly way
> to put it, but there is an analogous argument for independent
> modularity (you don't want the definition of a new superrole to
> invalidate objects that already exist).

Well spoken.


> Things get a little tricky when you think about "does" and "superdoes"
> together:
>
> role Foo does Bar superdoes Baz {...}
>
> What does that mean? Okay, Foo is a subset of Bar, and a superset of
> Baz. Already by simply declaring this we have mandated that "Baz does
> Bar". If Baz doesn't already conform to the interface of Bar, Foo
> better fill in the gaps for it. As for the ..., anything which
> conforms to it and Bar ought to be in Foo. So I think the equation
> ends up:
>
> Foo = Bar (*) (Baz (+) ...);
>
> In words, you are a Foo if you are a Bar and you either are a Baz or
> conform to my interface.

In the thread 'signature subtyping and role merging' I have argued
the need of merging method signatures that come in from different
superroles. This is less of a problem when creating superroles because
one can drop the method from the interface and maintain the subtyping
relation. So filling in the gaps is a hard task in general. I mean
in your example Foo has to define all methods required by Bar so that
Baz has at least the default provided by Foo. The gap between Baz and
Bar might also contain unresolvable name clashes that are of course
no problem as long as Bar and Baz are unrelated.


> Whew, I was really only going to reply with an "I dig it" sort of
> response, but I seem to have written some sort of complete theory. :-)

I missed you in the 'set operations for roles' thread. Your theory is
basically what was discussed there. Unfortunately nothing made it into
the synopsis except the dropping of '|' as the separator for
pre-composed roles. Actually not even the set operators are in S03.


A thing that is still unclear to me is how the issue of the supertype
intercepting lvalue uses of its methods is resolved. Preferably in a
declarative style. I mean a simple 'is rw' trait will hardly do because
you have to return a complete object of which only an attribute is
written to. The former subtype value has to be made available for the
lvalue method for constructing the new supertype lvalue.

Regards, TSa.
--

TSa

unread,
Dec 13, 2006, 7:40:00 AM12/13/06
to perl6-l...@perl.org
HaloO,

Starting from 'does' that means 'machen' in German and then
reversing it to 'vermachen' and looking that up in a dictionary
I end up at 'bequeath'. So it might read

role Foo bequeaths Bar {...}

But I'm non-native after all.

Regards, TSa.
--

Ruud H.G. van Tol

unread,
Dec 13, 2006, 8:30:35 AM12/13/06
to perl6-l...@perl.org
TSa schreef:
> Smylers:
>> Jonathan Lang:

>>> For the record, I think that "superdoes" should be spelled
>>> "done_by".
>>
>> I think it's unlikely that Larry will incorporate any keywords that
>> contain underscores -- certainly not without at least searching for a
>> single word that sums up the concept in question.
>
> Starting from 'does' that means 'machen' in German and then
> reversing it to 'vermachen' and looking that up in a dictionary
> I end up at 'bequeath'. So it might read
>
> role Foo bequeaths Bar {...}
>
> But I'm non-native after all.

delegated_to, performed_by, but why not just "by"?
(role Vigilante by Charles Bronson)

--
Groet, Ruud

Jonathan Lang

unread,
Dec 13, 2006, 9:35:27 AM12/13/06
to p6l
TSa wrote:
> Secondly I figure you are operating on the extension set a role
> defines. That is union produces a supertype aka the larger set
> of instances. This is in accordance to the usual type theory
> approach. Note that Jonathan operates on the intension set of roles
> that is union produces a subtype.

Assuming that I understand the terminology correctly, I'll go further
and say that one of the big differences between roles and subtypes
(using the terms in their perl 6 contexts) is that roles conceptually
operate on intension sets - everything about them is defined in terms
of their set of capabilities - while subtypes (i.e., objects with
"where" clauses) operate on extension sets - they're defined in terms
of the valid set of instances.

Incidently, I'm using terms like "supertype", "subtype", "superrole",
"subrole", etc. strictly due to a lack of a better alternative.
"Supertyping" only leads to a larger set of instances because a
parameter that asks for the supertype is willing to accept a subtype
in its stead.

> > Things work a little differently for required methods. When a
> > superrole requires a method be implemented, we (the language
> > designers) have a choise to make: it is illegal if the superrole
> > requires a method that the subroles don't implement or don't
> > themselves require, or it simply adds the new method to the required
> > list of the subroles which don't implement it. I'm inclined to say
> > the former, even though it seems a little more brittle.
>
> I can see no brittleness. The role that is supertyped is already
> fully defined when the superrole is about to be created. Thus it can
> be checked if required but unimplemented methods---that is ones with
> a {...} body---are present. These should be rejected on the rational
> that the pre-existing code can't retroactively be made to implement
> the method.

That's the brittleness: the set of methods that you are permitted to
define in the superrole is restricted to the set of methods in the
role being generalized. Since Num doesn't include methods re or im,
an attempt to include either method in a Complex superrole would be an
error according to this approach, or it would involve changing Num to
include methods re and im in the alternative approach.

The write-up that I saw in the original message seems to carry the
implication of the alternative approach (of back-editing the new
methods into the subrole). Note that if you go this route, you really
should allow yourself to define multiple versions of the new method:
one for the superrole, and one for each of the subroles used to define
it. I'd rather not go this route, though.

> > It means that
> > when you are implementing a role in a class, all you have to look at
> > for what you need to do is the role and the roles that it includes;
> > you don't need to scan the entire source for superroles which
> > encompass the one you are implementing. That is the user-friendly way
> > to put it, but there is an analogous argument for independent
> > modularity (you don't want the definition of a new superrole to
> > invalidate objects that already exist).
>
> Well spoken.

And agreed.

> > Things get a little tricky when you think about "does" and "superdoes"
> > together:
> >
> > role Foo does Bar superdoes Baz {...}
> >
> > What does that mean? Okay, Foo is a subset of Bar, and a superset of
> > Baz. Already by simply declaring this we have mandated that "Baz does
> > Bar". If Baz doesn't already conform to the interface of Bar, Foo
> > better fill in the gaps for it.

Sorry, but no. If Baz doesn't already conform to the interface of
Bar, you're dead in the water. "Filling in the gaps" amounts to
changing Baz, which is not in Foo's purview.

> In the thread 'signature subtyping and role merging' I have argued
> the need of merging method signatures that come in from different
> superroles. This is less of a problem when creating superroles because
> one can drop the method from the interface and maintain the subtyping
> relation. So filling in the gaps is a hard task in general. I mean
> in your example Foo has to define all methods required by Bar so that
> Baz has at least the default provided by Foo. The gap between Baz and
> Bar might also contain unresolvable name clashes that are of course
> no problem as long as Bar and Baz are unrelated.

This is why I think that having both "does" and "done_by" declarations
in a role definition is a mistake: the whole thing becomes a cryptic
mess far too quickly.

> A thing that is still unclear to me is how the issue of the supertype
> intercepting lvalue uses of its methods is resolved. Preferably in a
> declarative style. I mean a simple 'is rw' trait will hardly do because
> you have to return a complete object of which only an attribute is
> written to. The former subtype value has to be made available for the
> lvalue method for constructing the new supertype lvalue.

Look at it according to the normal flow: if you subtype a role with
read/write capabilities, the new role cannot have fewer rw
capabilities than the role that it's composing has. Reversing this,
if a role does not have a particular rw capability, then a superrole
cannot add it. This is a variation on the "adding methods to the
superrole" debate.

Mind you, once you've defined a supertype, you can freely create new
subtypes of it, and you can add whatever features you want to those
new subtypes, be they lvalue access, new methods, or anything else.
IMHO, the correct way to create an unordered Complex role from a Num
role is to use supertyping to remove the ordering capabilities from
Num, and then use subtyping to add "imaginary component" capabilities
to that. Yes, this means that a simple partial ordering check between
Complex and Num will result in a "no relation" result; but that's a
shortcoming of the type-checking system, not the type definition
system.

--
Jonathan "Dataweaver" Lang

Jonathan Lang

unread,
Dec 13, 2006, 9:36:08 AM12/13/06
to p6l

Entirely possible. OTOH, what we need here is something that very
clearly says "the reverse form of 'does'": if A does B, then B ___ A.
Far more important that if it's one word or two is: "what fits most
naturally in the gap?"

--
Jonathan "Dataweaver" Lang

Ruud H.G. van Tol

unread,
Dec 13, 2006, 10:23:54 AM12/13/06
to perl6-l...@perl.org
Jonathan Lang schreef:

> what we need here is something that very
> clearly says "the reverse form of 'does'": if A does B, then B ___ A.
> Far more important that if it's one word or two is: "what fits most
> naturally in the gap?"

follows, trails, tracks, enforces, obeys, tolerates, enacts, endorses,
passes, ratifies, allows, permits, takes, occupies, lets, shapes, molds,
munges, shadows, marks, filters, limits, brands, models, forms, sculpts,
figures, profiles, outlines, affects, carves, whittles, cuts, styles,
casts

--
Groet, Ruud

TSa

unread,
Dec 13, 2006, 12:10:03 PM12/13/06
to p6l
HaloO,

Jonathan Lang wrote:
> Assuming that I understand the terminology correctly, I'll go further
> and say that one of the big differences between roles and subtypes
> (using the terms in their perl 6 contexts) is that roles conceptually
> operate on intension sets - everything about them is defined in terms
> of their set of capabilities - while subtypes (i.e., objects with
> "where" clauses) operate on extension sets - they're defined in terms
> of the valid set of instances.

You understand the terminology correctly. I'm kind of split
of which approach to favor. I can understand Smylers objection
that the very existence of these two sets and the contradictory
results that set ops produce on them should prevent the usage of
set syntax for type construction. OTOH, I know of nothing else
that conveys the intent as terse.


> Incidently, I'm using terms like "supertype", "subtype", "superrole",
> "subrole", etc. strictly due to a lack of a better alternative.
> "Supertyping" only leads to a larger set of instances because a
> parameter that asks for the supertype is willing to accept a subtype
> in its stead.

Yes, this is how the argument goes. A supertype constraint allows
more instances to pass the test because the set of requirements is
smaller.


> That's the brittleness: the set of methods that you are permitted to
> define in the superrole is restricted to the set of methods in the
> role being generalized.

But that makes no sense at all because these are the ones which are
specialized further down in the subtyping chain. There has to be
some extra that the supertype defines for the subtypes.

> Since Num doesn't include methods re or im,
> an attempt to include either method in a Complex superrole would be an
> error according to this approach, or it would involve changing Num to
> include methods re and im in the alternative approach.

Then I did read Luke's post wrongly. Sorry.


> The write-up that I saw in the original message seems to carry the
> implication of the alternative approach (of back-editing the new
> methods into the subrole). Note that if you go this route, you really
> should allow yourself to define multiple versions of the new method:
> one for the superrole, and one for each of the subroles used to define
> it. I'd rather not go this route, though.

Hmm, looks like this is exactly where I intent to go. If we detach
method dispatch from the class that in the end provides the method
table for its instances we get the picture that the Complex supertype
creates dispatch targets for &im:(Num) and &im:(Complex) and since
Num <: Complex dispatch goes to the simple 0 returning role default
when called on a Num. So to support supertyping the lookup process
of methods has to scan upwards in the inheritance and role hierarchy
instead of just looking into a table in the class the object is blessed
into. But I think this is how dispatch works, doesn't it? The lookup
result might be cached for efficiency.


> Look at it according to the normal flow: if you subtype a role with
> read/write capabilities, the new role cannot have fewer rw
> capabilities than the role that it's composing has. Reversing this,
> if a role does not have a particular rw capability, then a superrole
> cannot add it. This is a variation on the "adding methods to the
> superrole" debate.

Well, and of the debate of how to retype an object. I see the .im
method in a position to do the right thing when given the invocant
and the rhs of the assignment. The thing I don't know is how the
syntax looks like. Does one write a STORE block into the method
body? And how is the container involved in the process?


> Mind you, once you've defined a supertype, you can freely create new
> subtypes of it, and you can add whatever features you want to those
> new subtypes, be they lvalue access, new methods, or anything else.

This is unquestioned. But my whole point is to get the Num nicely
embedded in the Complex without touching the Num implementation.


> IMHO, the correct way to create an unordered Complex role from a Num
> role is to use supertyping to remove the ordering capabilities from
> Num, and then use subtyping to add "imaginary component" capabilities
> to that. Yes, this means that a simple partial ordering check between
> Complex and Num will result in a "no relation" result; but that's a
> shortcoming of the type-checking system, not the type definition
> system.

Sorry again, the whole point is to get Num <: Complex in the first
place. How else should a subtyping directed dispatch system pick
methods? No relation can also be achieved with a unrelated
implementation of Complex.


Regards, TSa.
--

TSa

unread,
Dec 13, 2006, 12:16:47 PM12/13/06
to p6l
HaloO,

Luke Palmer wrote:
> Woah, that was a terrible passage. Let's try again. If your
> declaration is "role Foo superdoes Bar".
>
> * it is illegal to add a required method to Foo if Bar doesn't
> already implement it or require it

With "required method" you mean an unimplemented method with a {...},
right?

> * any required method for Foo automatically becomes a required
> method for Bar, even if it wasn't there before

Yes, I wouldn't allow yada methods in the superrole that are not
implemented in the subrole it is based on. Lifting a yada method
that exists in the subrole is fine of course.


Regards, TSa.
--

Jonathan Lang

unread,
Dec 13, 2006, 1:34:39 PM12/13/06
to p6l
TSa wrote:
> Jonathan Lang wrote:
> > Assuming that I understand the terminology correctly, I'll go further
> > and say that one of the big differences between roles and subtypes
> > (using the terms in their perl 6 contexts) is that roles conceptually
> > operate on intension sets - everything about them is defined in terms
> > of their set of capabilities - while subtypes (i.e., objects with
> > "where" clauses) operate on extension sets - they're defined in terms
> > of the valid set of instances.
>
> You understand the terminology correctly. I'm kind of split
> of which approach to favor. I can understand Smylers objection
> that the very existence of these two sets and the contradictory
> results that set ops produce on them should prevent the usage of
> set syntax for type construction. OTOH, I know of nothing else
> that conveys the intent as terse.

Agreed. The question is whether you think of a role as a set of
methods ("intension set") or as a set of instances ("extension set").
FWIW, it wasn't until this thread that I even registered that the
latter possibility existed. Given the above contrast between roles
and subtypes, I would really like to keep the extension set mindset
limited to subtypes and the intension set mindset limited to roles: a
role is an interface (i.e., a set of methods); a type is a set of
possible instances.

> > That's the brittleness: the set of methods that you are permitted to
> > define in the superrole is restricted to the set of methods in the
> > role being generalized.
>
> But that makes no sense at all because these are the ones which are
> specialized further down in the subtyping chain. There has to be
> some extra that the supertype defines for the subtypes.

No, there doesn't. For example, let's assume that whoever created the
Num role forgot to define an Order role for it. Supertyping would let
you create that role for yourself and would ensure that it has the
proper relationship with Num. That's what supertyping of roles should
be about: extracting subsets of its interface for use elsewhere (which
is part of the reason I'm leery about calling it "supertyping"; the
important part isn't the greater number of instances that it can have,
but instead the simpler interface that it provides).

> > The write-up that I saw in the original message seems to carry the
> > implication of the alternative approach (of back-editing the new
> > methods into the subrole). Note that if you go this route, you really
> > should allow yourself to define multiple versions of the new method:
> > one for the superrole, and one for each of the subroles used to define
> > it. I'd rather not go this route, though.
>
> Hmm, looks like this is exactly where I intent to go. If we detach
> method dispatch from the class that in the end provides the method
> table for its instances we get the picture that the Complex supertype
> creates dispatch targets for &im:(Num) and &im:(Complex) and since
> Num <: Complex dispatch goes to the simple 0 returning role default
> when called on a Num.

One problem with this is that the default return value for Num.im
ought to be zero; the default return value for Complex.im ought to be
whatever the imaginary component of the number is. So role Complex
would need both

method im (Complex: ) { return .im }
method im (Num: ) { return 0 }

That's what I was referring to in terms of having multiple copies of a
given method.

> Well, and of the debate of how to retype an object. I see the .im
> method in a position to do the right thing when given the invocant
> and the rhs of the assignment. The thing I don't know is how the
> syntax looks like. Does one write a STORE block into the method
> body? And how is the container involved in the process?

Synopsis 6, "Lvalue subroutines".

> > Mind you, once you've defined a supertype, you can freely create new
> > subtypes of it, and you can add whatever features you want to those
> > new subtypes, be they lvalue access, new methods, or anything else.
>
> This is unquestioned. But my whole point is to get the Num nicely
> embedded in the Complex without touching the Num implementation.

No can do. As soon as you add anything to Complex that isn't in Num,
you've changed Num.

> > IMHO, the correct way to create an unordered Complex role from a Num
> > role is to use supertyping to remove the ordering capabilities from
> > Num, and then use subtyping to add "imaginary component" capabilities
> > to that. Yes, this means that a simple partial ordering check between
> > Complex and Num will result in a "no relation" result; but that's a
> > shortcoming of the type-checking system, not the type definition
> > system.
>
> Sorry again, the whole point is to get Num <: Complex in the first
> place. How else should a subtyping directed dispatch system pick
> methods? No relation can also be achieved with a unrelated
> implementation of Complex.

I agree that "no relation" is not the desired outcome here - but
neither is 'Num <: Complex'. The implication of 'Num <: Complex' is
that Num can do anything that Complex can do, which isn't true: e.g.,
Num can't assign a value to the imaginary component, because Num
doesn't _have_ an imaginary component. (This is especially important
when you consider that Num is an immutable type: you can't just mutate
it into a Complex when the need arises.)

In addition, there can be some situations where you want Num to
complain if asked to call .im. If you change Num so that .im now gets
successfully called, code that relied on it not working will break.

When supertyping, the only thing that should ever change about an
existing type is that it should now be callable in contexts that are
looking for the new supertype. The only way to ensure this is to
require the supertype's interface to be a subset of the existing
type's interface.

Consider this: once you've modified Num to have both a real and an
imaginary component (even if that imaginary component is always zero),
how do you include a real number (not a complex masquerading as a
real) in a role definition? Remember also that Int does Num.
There's no telling what havoc you'll wreak if you're not careful.

--
Jonathan "Dataweaver" Lang

Larry Wall

unread,
Dec 13, 2006, 1:46:17 PM12/13/06
to perl6-l...@perl.org

And I'm not even gonna do that if I can't be convinced of a use case
beyond "Num does Complex". And since my sinuses are full of the crud
that is going around right now, I'm not in a very convincable mood.
With the little sense of smell I have left, this smells like INTERCAL's
"COME FROM" statement to me, trading a teeny bit of notational convenience
in one spot for oversized headaches elsewhere. Most Perl programmers
are not interested in type theory, and complexifying the little bit of
intentional typing we're introducing with roles will simply cause most
people to avoid it like the plague. We might *possibly* get away with
reopening roles like we can reopen a class:

role Num is also does Complex {
method im {...}
}

but roles are really supposed to be fairly immutable in the Perl 6
scheme of things, so such a declaration would probably have to require
that the Num role never have been composed into anything else yet.

Or we could say that you can't reopen the Num role; you can only
reopen the Num class and mix in the Complex role. That's where it
stands at the moment.

Larry

Luke Palmer

unread,
Dec 13, 2006, 5:45:33 PM12/13/06
to Jonathan Lang, perl6language
In spite of Larry's comments, I will continue to entertain this idea
until it is solid to myself that I would be comfortable accepting it.

On 12/13/06, Jonathan Lang <dataw...@gmail.com> wrote:
> Agreed. The question is whether you think of a role as a set of
> methods ("intension set") or as a set of instances ("extension set").
> FWIW, it wasn't until this thread that I even registered that the
> latter possibility existed. Given the above contrast between roles
> and subtypes, I would really like to keep the extension set mindset
> limited to subtypes and the intension set mindset limited to roles: a
> role is an interface (i.e., a set of methods); a type is a set of
> possible instances.

Eh, a proposal is nothing if you can't make sense of it from both
mindsets, because they both are real. While you may think of a role
as an interface, it still does have objects that can obey it, so the
set of such objects still exists. My brain seems to prefer the
extensional mindset, but it's important to try to make sense of
everything from both. Hmm, the extensional is the more "spookily
theoretical" of the two, since Perl will know the interface, but it
won't create the set of all possible, eventual instances.

> > But that makes no sense at all because these are the ones which are
> > specialized further down in the subtyping chain. There has to be
> > some extra that the supertype defines for the subtypes.
>
> No, there doesn't. For example, let's assume that whoever created the
> Num role forgot to define an Order role for it. Supertyping would let
> you create that role for yourself and would ensure that it has the
> proper relationship with Num. That's what supertyping of roles should
> be about: extracting subsets of its interface for use elsewhere (which
> is part of the reason I'm leery about calling it "supertyping"; the
> important part isn't the greater number of instances that it can have,
> but instead the simpler interface that it provides).

Note that I *am* arguing for a back-editing behavior. You are of
course allowed to remove anything (by not declaring it), and you are
also allowed to add new methods as long as they have a default
implementation. You are not allwed to add new unimplemented, or
required, methods.

The reasoning for this is mostly utility. How are you supposed to
generalize Num to Complex (okay, fine, I'll try to come up with
another example. Is it okay if it is also from math?) if Num didn't
know that an "imaginary part" existed?

You could really consider that syntax sugar if you like:

role Foo {
method foo() { "hello" }
}
role Bar superdoes Foo {
method bar() { "world" }
}

Is equivalent to declaring Bar with no methods, and then adding:

multi sub bar(Bar:) { "world" }

Modulo some minor differences, but the semantics are essentially the
same. I don't think we'd want to disable back-editing because then we
would be encouraging the idiom I just demonstrated, which is less
clear (IMO).

Back-editing seems weird from an intensional mindset, but perfectly
natural from an extensional mindset (you are just defining functions
on things). If you don't think of a role as a set of methods, but
rather a tree of sets of methods (like inheritance), then back-editing
seems more natural (you just insert a different parent above the old
role).

> One problem with this is that the default return value for Num.im
> ought to be zero; the default return value for Complex.im ought to be
> whatever the imaginary component of the number is. So role Complex
> would need both
>
> method im (Complex: ) { return .im }
> method im (Num: ) { return 0 }

Good point. Hmm, that's a bit awkward.

It can actually be averted in the Complex case, by defining the .im
attribute to default to 0. But that is a hack that will not
generalize.

For now (because of this example, in fact), I'm inclined to change the
proposal to "please don't design the language to prevent a module from
implementing supertyping". I think disallowing reopening of roles
will prevent that.

Maybe we allow reopening of roles, but new method declarations have to
come with defaults; that way you are not (as above) invalidating
existing objects, just adding behavior to them. That is a tricky
issue, though, because such definitions might add conflicts and
invalidate those objects anyway. Maybe more-recently added methods
have less priority than older ones. That stinks. Hmm. I need to
think about this.

Luke

Jonathan Lang

unread,
Dec 14, 2006, 2:13:32 AM12/14/06
to p6l
Luke Palmer wrote:

> Jonathan Lang wrote:
> > Agreed. The question is whether you think of a role as a set of
> > methods ("intension set") or as a set of instances ("extension set").
> > FWIW, it wasn't until this thread that I even registered that the
> > latter possibility existed. Given the above contrast between roles
> > and subtypes, I would really like to keep the extension set mindset
> > limited to subtypes and the intension set mindset limited to roles: a
> > role is an interface (i.e., a set of methods); a type is a set of
> > possible instances.
>
> Eh, a proposal is nothing if you can't make sense of it from both
> mindsets, because they both are real. While you may think of a role
> as an interface, it still does have objects that can obey it, so the
> set of such objects still exists. My brain seems to prefer the
> extensional mindset, but it's important to try to make sense of
> everything from both. Hmm, the extensional is the more "spookily
> theoretical" of the two, since Perl will know the interface, but it
> won't create the set of all possible, eventual instances.

OK. With this in mind, I suppose that set operations, per se, are
indeed out. Let's see what can be done with "does" and "done_by" (for
lack of a better name).

First, note that

role Foo does Bar does Baz { ... }

is exactly the same as

role Foo {
does Bar;
does Baz;
...
}

Likewise, I'd expect

role Foo done_by Bar { ... }

to be equivalent to

role Foo { done_by Bar; ... }

or

role Foo { ... ; done_by Bar }

The contents of the curly braces should always define the interface
that the role brings to the table. Furthermore, "done_by" statements
should have no effect whatsoever on Foo's interface, other than to
define restrictions on what is legal in Foo (maybe). In extension set
terminology, the above means that "Foo = ...; Bar (+)= Foo". In
effect, "Foo done_by Bar" means "modify Bar to include 'does Foo'".

In this way, mixtures of "does" and "done_by" work in an intuitive
way: only "does" helps establish what the role is capable of, while
"done_by" feeds those capabilities into another, existing role.

Which gets back to the issue of whether roles should be mutable or
not. If roles are immutable, then "done_by" is going to have to levy
some rather heavy-handed restrictions on the new role, specifically to
avoid new capabilities being back-edited into existing roles. If
roles are mutable, "done_by" needn't impose any restrictions - but the
creator of the new role has the responsibility to ensure that any
additions that he makes doesn't break any existing classes.

Unfortunately, this approach completely misses the boat in terms of
what I was looking for in supertyping - namely, the ability to reuse
portions of an existing interface (such as the aforementioned "extract
an Order role out of the Num role") - useful when using modules where
the module designer overcomplicated things, but you don't want to
rebuild everything from scratch. That, I think, is the common feature
of both goals: the ability to compensate for another coder who didn't
do things exactly the way you want them done, but who was reasonably
close.

> Note that I *am* arguing for a back-editing behavior. You are of
> course allowed to remove anything (by not declaring it), and you are
> also allowed to add new methods as long as they have a default

> implementation. You are not allowed to add new unimplemented, or
> required, methods.

Note that the "default implementation" requirement need only be
specified in terms of the done_by roles: "method foo(Foo: ) { ... }"
is perfectly legitimate, as long as &Foo::foo:(Bar: ) and
&Foo::foo:(Baz: ) are more solidly defined, given "Foo done_by(Bar)
done_by(Baz)".

(BTW: as I see it, all methods defined in Foo are "required". I'd
prefer "unimplemented" or "abstract" as the description for methods
with '{ ... }' as the body.)

> The reasoning for this is mostly utility. How are you supposed to
> generalize Num to Complex (okay, fine, I'll try to come up with
> another example. Is it okay if it is also from math?) if Num didn't
> know that an "imaginary part" existed?

I suspect that Num vs. Complex is considered to be a poor choice for
an example because Num and Complex are going to be standard types
already.

> You could really consider that syntax sugar if you like:
>
> role Foo {
> method foo() { "hello" }
> }
> role Bar superdoes Foo {
> method bar() { "world" }
> }
>
> Is equivalent to declaring Bar with no methods, and then adding:
>
> multi sub bar(Bar:) { "world" }
>
> Modulo some minor differences, but the semantics are essentially the
> same. I don't think we'd want to disable back-editing because then we
> would be encouraging the idiom I just demonstrated, which is less
> clear (IMO).

My understanding is that subs can't have invocants; only methods and
submethods can, and both of those must be defined within a role or
class.

> Back-editing seems weird from an intensional mindset, but perfectly
> natural from an extensional mindset (you are just defining functions
> on things). If you don't think of a role as a set of methods, but
> rather a tree of sets of methods (like inheritance), then back-editing
> seems more natural (you just insert a different parent above the old
> role).

As you say, though, both mindsets should be accommodated. Part of the
beauty of role composition, as opposed to class inheritance, is that
you _don't_ have to think of it as a hierarchy of sets; it all
flattens out into a single, consistent set. From the "roles as
interfaces" perspective, the wierdest aspect of back-editing is that
roles are mutable now; you can't assume that just because a role
doesn't have a given capability when you first load it into your
program, that it will never have that capability. You _can_ assume
that whatever capabilities it does have won't change; back-editing can
add new tools, but it can't change existing ones.

> > One problem with this is that the default return value for Num.im
> > ought to be zero; the default return value for Complex.im ought to be
> > whatever the imaginary component of the number is. So role Complex
> > would need both
> >
> > method im (Complex: ) { return .im }
> > method im (Num: ) { return 0 }
>
> Good point. Hmm, that's a bit awkward.

It's also syntax that S12 already permits, for the purpose of Dogwood
disambiguation.

> For now (because of this example, in fact), I'm inclined to change the
> proposal to "please don't design the language to prevent a module from
> implementing supertyping". I think disallowing reopening of roles
> will prevent that.
>
> Maybe we allow reopening of roles, but new method declarations have to
> come with defaults; that way you are not (as above) invalidating
> existing objects, just adding behavior to them. That is a tricky
> issue, though, because such definitions might add conflicts and
> invalidate those objects anyway. Maybe more-recently added methods
> have less priority than older ones. That stinks. Hmm. I need to
> think about this.

I suspect that this is the core of the problem: if you back-edit a new
method into a role, you don't necessarily have to give it a default
(roles can be abstract); but you _do_ have to make sure that every
class that already does that role (directly or indirectly) has a valid
definition for the new method (classes must be concrete). This is the
responsibility of the creator of the new role; and since classes are
open by default, it's doable: you just have to make sure to have a
series of "class ___ is also ..." statements that precede the new
role's definition, inserting the appropriate method definitions. If
roles are open (or can be opened), then you can likewise amend them
preemptively to resolve conflicts that the new role will introduce:

role Num { ... }
...
role Num is also { method im() { return 0 } }
role Complex done_by Num {
method im() { ... }
...
}

If "back-editing" is allowed, then roles ought to be able to be open.
It isn't strictly necessary; but it reduces the clumsiness of
"back-editing" by avoiding the need to handle all of the newly
introduced conflicts in the classes.

Of course, with open roles, the above could also be:

role Num { ... }
...
role Complex {
method im() { ... }
...
}
role Num is also { does Complex; method im() { return 0 } }

This, perhaps, would be the most intuitive way of handling
"back-edit"-capable supertyping: no new syntax is involved.

--
Jonathan "Dataweaver" Lang

TSa

unread,
Dec 14, 2006, 6:50:26 AM12/14/06
to perl6language
HaloO,

Luke Palmer wrote:
> For now (because of this example, in fact), I'm inclined to change the
> proposal to "please don't design the language to prevent a module from
> implementing supertyping". I think disallowing reopening of roles
> will prevent that.

I might not have formulated it this way but this was my intent
from the start. Since classes constitute types and are partially
ordered for dispatch purposes one way of achieving it is by

class Num also does Complex
{
method re is rw { return self }
method im is rw
{
return new Proxy:
FETCH => method { return 0 },
STORE => method { return new Complex(self,0).im };
}
}

This actually looks better because the .re method has a more
natural implementation. In my superrole code I had a { return
self as Num }.


> Maybe we allow reopening of roles, but new method declarations have to
> come with defaults; that way you are not (as above) invalidating
> existing objects, just adding behavior to them. That is a tricky
> issue, though, because such definitions might add conflicts and
> invalidate those objects anyway. Maybe more-recently added methods
> have less priority than older ones. That stinks. Hmm. I need to
> think about this.

No, errors should be blamed on the role that does add the parent
relationship. That is a name clash with an existing method further
down the does-hierarchy should result in a compile error for the newly
created role. Since the complete does-hierarchy is only known after
CHECK time of the main program this is where it will be reported.

This brings up the question of how to resolve the error from the top
of the hierarchy. The simplest form is some renaming. For a standard
type like Complex the names .im and .re might be reserved. IOW, it's
documented that a 'use Complex' might result in method conflicts
because it adds these names from the top. Then the standard type can
be implemented in a module.

Well, if the conflict occurs in a class that is under the authority
of the programmer she might resolve it there of course. But that
shouldn't be mandatory.


Regards, TSa.
--

TSa

unread,
Dec 14, 2006, 7:06:10 AM12/14/06
to perl6-l...@perl.org
HaloO,

Larry Wall wrote:
> role Num is also does Complex {
> method im {...}
> }

Is that the actual syntax? I mean is it the keyword pair
'is also' or does 'also' by itself have a meaning? With
a more natural 'role Num also does Complex'.


> but roles are really supposed to be fairly immutable in the Perl 6
> scheme of things, so such a declaration would probably have to require
> that the Num role never have been composed into anything else yet.

Doesn't a role also do the package role? That is we might just
keep the uncomposed roles and their does-hierarchy around for
later method lookup.


> Or we could say that you can't reopen the Num role; you can only
> reopen the Num class and mix in the Complex role. That's where it
> stands at the moment.

BTW, how is it achieved to have a class Num and a role Num?
Can one just write

role Blahh {...}

class Blahh does Blahh {...}

or is there some hidden magic for built-in types? Array, Hash etc.
seem to pull the same stunt---but how?


Regards, TSa.
--

Smylers

unread,
Dec 14, 2006, 7:26:26 AM12/14/06
to perl6-l...@perl.org
TSa writes:

> Larry Wall wrote:
>
> > role Num is also does Complex {
> > method im {...}
> > }
>
> Is that the actual syntax?

Larry's words that you snipped introducing that code fragment were:

We might *possibly* get away with reopening roles like we can reopen a
class:

It seems unlikely that the syntax is final if the feature is only being
mooted as "might be possible" (and the rest of Larry's message wasn't
exactly enthusiastic about the idea).

Smylers

TSa

unread,
Dec 14, 2006, 10:13:00 AM12/14/06
to perl6-l...@perl.org
HaloO,

Larry Wall wrote:
> Or we could say that you can't reopen the Num role; you can only
> reopen the Num class and mix in the Complex role. That's where it
> stands at the moment.

This is not too bad an outcome. One question though: is the
augmentation of the class lexically scoped? Or does a module
that contains the line

class Num is also does Complex {...}

infect all other uses of the class because the class is
global? BTW, could we make that read

class Num also does Complex {...}


Regards, TSa.
--

Larry Wall

unread,
Dec 16, 2006, 2:12:40 PM12/16/06
to p6l
On Wed, Dec 13, 2006 at 11:13:32PM -0800, Jonathan Lang wrote:
: Unfortunately, this approach completely misses the boat in terms of

: what I was looking for in supertyping - namely, the ability to reuse
: portions of an existing interface (such as the aforementioned "extract
: an Order role out of the Num role") - useful when using modules where
: the module designer overcomplicated things, but you don't want to
: rebuild everything from scratch. That, I think, is the common feature
: of both goals: the ability to compensate for another coder who didn't
: do things exactly the way you want them done, but who was reasonably
: close.

I've been sloshing all this through my muddled head again, and I'm
beginning to see this as more of a naming policy issue, that is, an
authority issue. The problem with "done_by" is that you're modifying
something in place, which mixes up the one name to mean two different
things. But this sort of problem is almost exactly what the naming
authority was added for. Suppose JRANDOM's version of Num says:

role Num-1.3-JRANDOM does OUTER::Num does Complex;
method re (--> OUTER::Num) { self }
method im (--> OUTER::Num) { 0.0 }
...

Then if you say

use Num-1.3-JRANDOM;

then for the rest of your lexical scope, Num is known to do Complex,
without backediting of the standard Num role. The only "downside"
I can see is lack of Magical Action at a Distance--other lexical
scopes will also have to declare the Num alias, either directly or
via a policy module.

The OUTER:: notation above is negotiable. I originally had Num-*-STD
but that gets a bit odd when you want to say:

role Num-1.3-JRANDOM does Num-*-STD does Complex;
method re (--> Num-*-STD) { self }
method im (--> Num-*-STD) { 0.0 }

insofar as it visually implies a * every time, and in theory each *
could resolve to a different version number if the compiler was really
slow and the library updater was really fast. One can get rid of that
visual impression by saying

role Num-1.3-JRANDOM does Num-6-STD does Complex;
method re (--> Num-6-STD) { self }
method im (--> Num-6-STD) { 0.0 }

though that's still effectively wildcarded for versions of Perl 6.*.*.
I also thought about just saying:

role Num-1.3-JRANDOM does perl6:Num does Complex;
method re (--> perl6:Num) { self }
method im (--> perl6:Num) { 0.0 }

but that seems wrong. The perl6: prefix denotes the current namespace,
not the standard namespace, so since declarations insert their name
immediately in Perl 6, the new role Num-1.3-JRANDOM would already
be installed such that you could say perl6:Num-1.3-JRANDOM, and
arguably perl6:Num could select our own name instead of Num-6.0.2-STD.
That would seem to depend on site policy not globally overriding STD
with JRANDOM. In any event, this certainly can't work:

role Num-1.3-JRANDOM does Num does Complex;
method re (--> Num) { self }
method im (--> Num) { 0.0 }

because Num is already aliased to Num-1.3-JRANDOM by then. So that's
why I ended up using OUTER::. However, in the spirit of TMTOWTDI,
perhaps there wants to be a way to treat a naming authority as defining
a "language" of sorts, so perhaps we could allow the naming authority
out front as if it were specifying the language:

use JRANDOM:Num;
use JRANDOM:Num-1.3;

which would be short for

use Num-*-JRANDOM;
use Num-1.3-JRANDOM;

Then we'd write our "done_by" role above as:

role Num-1.3-JRANDOM does STD:Num does Complex;
method re (--> STD:Num) { self }
method im (--> STD:Num) { 0.0 }

and there's no visually disturbing * there--though of course it's still
there semantically. Probably we need to say that once a compilation
unit looks up a library name and resolves the version and authority,
it should stay resolved to that same long name for the rest of the
compilation. That is, STD:Num becomes an alias to Num-6.0.2-STD or
whatever for the rest of the compilation. Or at least the rest of
the lexical scope...not sure how I feel about two classes in the
same compilation unit getting a different version of Num, but it
could happen if we limit the alias to the lexical scope and each is
of the form

class Foo {
does JRANDOM:Num;
...
}
class Bar {
does JRANDOM:Num;
...
}

so maybe such aliases are hoisted to the outermost lexical scope or
some such, so they latch on for the whole compilation unit at least.
Alternately, we put some kind of transactional lock on the library
so it can't appear to change state in the middle of compilation.
That might be the wiser course.

Larry

Jonathan Lang

unread,
Dec 16, 2006, 7:53:28 PM12/16/06
to p6l
Larry Wall wrote:
> I've been sloshing all this through my muddled head again, and I'm
> beginning to see this as more of a naming policy issue, that is, an
> authority issue. The problem with "done_by" is that you're modifying
> something in place, which mixes up the one name to mean two different
> things. But this sort of problem is almost exactly what the naming
> authority was added for. Suppose JRANDOM's version of Num says:
>
> role Num-1.3-JRANDOM does OUTER::Num does Complex;
> method re (--> OUTER::Num) { self }
> method im (--> OUTER::Num) { 0.0 }
> ...
>
> Then if you say
>
> use Num-1.3-JRANDOM;
>
> then for the rest of your lexical scope, Num is known to do Complex,
> without backediting of the standard Num role. The only "downside"
> I can see is lack of Magical Action at a Distance--other lexical
> scopes will also have to declare the Num alias, either directly or
> via a policy module.

So if I'm understanding you correctly, what I'm looking for would be
something along the lines of:

Scenario: someone has created a module with a Num role, complete with
all the comparison operators needed to do ordering, etc.; but they
neglected to consider the possibility of factoring out the ordering
capabilities - that is, they failed to define a separate Order role
that Num composes. Due to Perl 6's nominative type system - i.e.,
_not_ duck-typing - I would not be able to simply create an Order role
with the appropriate methods and expect "sub sort(Order @list) {
sortit }" to be able to accept a list of Num. To get around this, I'd
like to insert "does Order" into the definition of Num:

# define a generic Order role
role Order {
method infix:<cmp> ($rhs) { doit }
...
}

#<create a specialized version of Num that composes Order, and
resolve the resulting conflicts between Order and the original version
of Num>
role Num-1.0-dataweaver does OUTER::Num does Order {
method infix:<cmp> ($rhs) { return OUTER::Num.cmp $_: $rhs }
...
}
...
use Num-1.0-dataweaver;

Outer::Num still won't be usable in the sort routine; but it will be
hidden by Num-1.0-dataweaver, which behaves identically to OUTER::Num
in all respects except that it _can_ be used in place of Order:

my Num @x; # Num refers to Num-1.0-dataweaver instead of OUTER::Num.
sort @x;

That's... doable. It seems like an awful lot of work just to expand
the types that an existing role will match; but at least it's
possible, after a fashion.

--

What I'd rather have would be something along the lines of:

role Num { ... }
...
role Order { ... }
...
Num.cando(Order);

The last line would be to .does() as the assignment operator is to the
equality operator, except that .cando() would fail unless Num can
already do all of Order's methods. I.e.: if (and only if) Num already
matches Order in terms of duck-typing, .cando (or whatever) would
legitimize the relationship in terms of the nominative type-checking,
such that 'Num.does(Order)' would thereafter be true.

--
Jonathan "Dataweaver" Lang

TSa

unread,
Dec 20, 2006, 11:38:20 AM12/20/06
to p6l
HaloO,

Larry Wall wrote:
> Then we'd write our "done_by" role above as:
>
> role Num-1.3-JRANDOM does STD:Num does Complex;
> method re (--> STD:Num) { self }
> method im (--> STD:Num) { 0.0 }

The first issue I see with this approach is that we get one
more contender for the plain name Complex because I think
one wants to define an authority Complex along with a role
and a class. I.e. I would want the import site to read:

use Complex:Num;

Which also brings us to the second issue. To actually use
the Complex itself one then needs to write:

use Complex;
use Complex:Num;

unless Complex is a module that provides in addition to
homonymous role and class Complex a modified version of
Num under authority Complex. How would such a module be
defined?

module Complex
{
role Complex {...} # for the type
class Complex does Complex {...} # for instanciation
role Num-1.3-Complex does STD:Num does Complex


{
method re (--> STD:Num) { self }
method im (--> STD:Num) { 0.0 }
}
}


Regards, TSa.
--

0 new messages