TSa wrote:
Jonathan Lang wrote:
> What do you mean by "uncomposed class"?

The self always refers to the object as instance of the composed
class. Methods are therefore resolving to the outcome of the
composition process. But super in a role refers to methods from
the class definition even when the final method comes from method
combination in the composition process.

Still not following.  Can you give an example?

> I hope not; that's exactly what declaring an unimplemented method in
> a role is supposed to do.

My idea is that the type system calculates a type bound for the class
from the definition of the role. That includes attributes and their
accessor methods. It's a matter of taste how explicit this interface
is declared or how much of it is inferred.

Note that it's entirely possible for attributes to not make it into
the final class, if the accessor methods get redefined in such a way
as to remove reference to the attributes.  This is part of the notion
that roles supply an outline of what the class should do, but only the
class actually supplies the definitive details of how to do it.

As I see it: "is" and "does" declarations in a role impose
requirements on the final class: it must derive from another class
("is"), or it must compose another role ("does").  "method" and "has"
are each one part requirement and one part suggestion: for "method",
the class is required to include a method that matches the given name
(which, of course, includes the method's signature), and a particular
closure is suggested that the class can accept or override.

For "has", the class is required to provide accessor methods
corresponding to the attribute's read/write capabilities (a rw method
if it's a rw attribute; a regular method if it's a regular attribute;
and no method if it's a private attribute); like any other method, a
closure is suggested that the class may accept or override.  In
addition, the role suggests that a given attribute be added to the
class' state information.  This suggestion is implicitly accepted if
any of the methods that are used by the final class refer to the
attribute; it is implicitly rejected if none of them do.  The same
rule applies to private methods.

Thus, the only things that I'd recommend using to calculate a
type-boundary would be the superclasses (provided by "is") and the
method names (provided by "method" and "has").

>  (And declaring an implemented method does
> the same thing, with the addition that it also suggests an
> implementation that the class is free to use or ignore as it sees
> fit.)

We have a priority conflict here. The question is if the class or the
role is seeing the other's methods for method combination.

I believe that I address this later on; if not, please clarify.

>> (There does need to be a way to call, in a Role A, both the "blah"
>>  defined in A and whatever the "blah" the final class may use.

Yes, this is the subject of the current debate. I'm opting for a
method combination semantics that allows the role to call the class
method.

Agreed.

>> $self.blah() is the later, $self.A::blah() or similar is likely to
>> be the former.)
>
> No, there doesn't.  Given that C<class Foo does A> and C<role A does
> B>, There needs to be a way to call, in class Foo, both the "blah"
> defined in Foo, the "blah" defined in A (so that Foo can reimplement
> A's version as a different method or as part of its own), and the
> "blah" defined in B;

 From the class all composed parts are available through namespace
qualified names. But a role is a classless and instanceless entity.
The self refers to the objects created from the composed class. The role
is not relevant in method dispatch. That is a method is never dispatched
to a role. But the role should be able to participate in the method
definition of the composed class.

Also agreed.  In particular, I'm referring to _how_ a role should
participate in the method definition of the composed class; I am not
referring to method dispatch, which is limited to the class hierarchy.

> and there needs to be a way to call, in role A,
> both the "blah" defined in Foo and the "blah" defined B; but role A
> does not need a way to explicitly call a method defined in A.

I'm not sure if I get this right. But as I said above a role can not
be dispatched to. Which method do you think should take precedence
the role's or the class's? That is who is the defining entity in the
method combination process?

I agree with the idea behind the current definition of this: if the
class provides its own definition for a method, that should take
precedence over the role's definition.  If it doesn't, then it adopts
the role's definition as its own.

I would hope it is the role if a as of now
unknown syntax has declared it. Perhaps it should be even the default.
The rational for my claim is that a role is composed several times
and then every class doing the role automatically gets the correct
version. Otherwise all classes are burdened with caring for the role's
part in the method.

Huh?

>  It
> should assume that if Foo overrides A's implementation of blah, Foo
> knows what it's doing; by the principle of least surprise, Foo should
>  never end up overriding A's implementation of blah only to find that
>  the original implementation is still being used by another of the
> methods acquired from A.

Could you make an example because I don't understand what you mean with
original implementation and how that would be used by role methods.

   role A {
     method foo() { say "Ah..." }
     method bar() { $self.foo() }
   }

   class Foo does A { }

   class Bar does A {
     method foo() { say "Oh..." }
   }

   class Baz does A {
     method foo() {
       $self.A::foo();
       say "Choo!";
     }
     method baz() { $self.A::foo() }
   }

The above should be exactly equivalent to:

   class Foo {
     method foo() { say "Ah..." }
     method bar() { $self.foo() }
   }

   class Bar {
     method foo() { say "Oh..." }
     method bar() { $self.foo() }
   }

   class Baz {
     method foo() {
       $self!'A::foo'();
       say "Choo!";
     }
     method bar() { $self.foo() }
     method baz() { $self!'A::foo'() }
     my method 'A::foo'() { say "Ah..." }
   }

(The optimizer should remove the extraneous curly braces.)

So:

   Foo.foo(); # "Ah...\n"
   Foo.bar(); # "Ah...\n"
   Bar.foo(); # "Oh...\n"
   Bar.bar(); # "Oh...\n"
   Baz.foo(); # "Ah...\nChoo!\n"
   Baz.bar(); # "Ah...\nChoo!\n"
   Baz.baz(); # "Ah...\n"

In Baz, C<my method 'A::foo'()> represents the original implementation
of foo(), while C<method foo()> represents the final implementation
chosen by the class.

If you were to allow a role method to directly refer to its own
implementation, you could do something like:

role A {
 method foo() { say "Ah..." }
 method bar() { $self.A::foo() }
}

Substituting this version of role A into my original example would
lead to this sort of output:

   Foo.foo(); # "Ah...\n"
   Foo.bar(); # "Ah...\n"
   Bar.foo(); # "Oh...\n"
   Bar.bar(); # "Ah...\n"
   Baz.foo(); # "Ah...\nChoo!\n"
   Baz.bar(); # "Ah...\n"
   Baz.baz(); # "Ah...\n"

On reflection, this wouldn't neccessarily be as bad as I originally
thought.  I don't see why anyone would want to use it; but it's
identified for what it is clearly enough that someone reading the role
will know what to expect when he starts making changes to things.

Method dispatch is on the class never on the role. As far as dispatch is
concerned the role is flattend out. But the question is how the class's
method is composed in the first place.

Agreed.  Hopefully, the above sheds some light on where I'm coming from.

--

Re: Partial Ordering
I'm not sure if this ordering of roles can be called duck typing
because it would put roles that have the same content into the
same lattice node. The well known bark method of Dog and Tree
comes to mind.

IIRC, duck typing is based on how well the set of available method
names match, its main flaw coming from the possibility of homonymous
methods.  With the Dog and Tree example, consider the possibility that
both versions of "bark" take no parameters other than the invocant,
and neither version returns anything.  Their signatures would thus be
identical (since the invocant's type is the final class' type).

But the arrow types of the methods will be different.
One has type :(Dog --> Dog) the other :(Tree --> Tree) and a joined
node will have type :(Dog&Tree --> Dog|Tree). This might just give
enough information to resolve the issues surrounding the DogTree
class.

Not sure what you mean by "the arrow types".  Are you referring to
embedding a return type in the signature?

> By "most specific", you'd mean "closest to the top"?

No, closer to the bottom. The join operator | of the lattice produces
subtypes with a larger interface that is more specific. It's like
the more derived class in a class hierarchy.

Got it.

--
Jonathan "Dataweaver" Lang

Reply via email to