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

Reply via email to