HaloO,

Jonathan Lang wrote:
Still not following.  Can you give an example?

The example in the original post contains a class GenSquare that
has got an equal method that checks the sides of the self square
and the incoming argument square. The GenPointMixin role provides
an equal method that compares the x and y coordinates. The correct
final method equal of the composed class needs to check sides and
coordinates. If the simple side checking equal of the class
overrides the closure from the role there is a false implementation.
OTOH, forcing all classes to call the role closure is bad design of
the composition process. Hence I'm arguing for a super keyword that
in a role refers to the class. In the example super.equal($p) calls
the side checking closure.


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.

I see that you regard the class as the ultimate definer of
functionality. The role requires a method including its signature
but the class implements it. For the type system the role encodes
the guarantee of the method availability.


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.

I almost agree here. The only thing I'm asking for is that the role's
closure is not discarded so easily. The designer of the role takes
responsibility for the role's part in the final method closure. The
combination process should produce the correct result automatically
and independent of the class's cooperation.


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").

That makes classes too typeish. The type system is mostly based on
roles. The class inheritance graph should not be used for typing.
Or do you want constraints on the class derivation process that
guarantees subclasses to be subtypes? As I read it the class derivation
is free to violate replaceability of subclasses where superclasses
are expected. Roles are a guarantee of functionality not classes.


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?

Yeah! The role adds a certain aspect to the correct implementation of
a method. And so does the class. But it is the role that is composed
into the class not the other way around. A role is intended to be
composed several times into completely different classes. With blind
precedence to class methods the role's aspects are lost and have to
be reintroduced in each and every class. I consider that inconvenient
and error prone.


>  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() }
   }

Isn't that self.foo() without the sigil? It is clear that .foo
is dispatched on the class.

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

What is A referring to here? Baz doesn't compose role A here.
And why the exclamation mark?


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.

OK, thanks for the example.


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() }
}

Yes, this should call the role closure from within the class.


With precedence to class methods my original example should read

role GenPointMixin
{
   has Int $.x;
   has Int $.y;
   method class_equal ( : ::?CLASS $p ) {...}
   method equal( : ::?CLASS $p --> Bool )
   {
      return self.class_equal(p) and
             self.x == $p.x and self.y == $p.y;
   }
}

which is clumsy and relies on the fact that the class is *not*
overriding the equal method but of course provides the class_equal
method. I want that combination process to be available under the
equal name slot. Which requires the role to take precedence at least
for the equal method. The question is what syntax to use for this
feature. I propose a 'is augmented' trait on the role method.


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).

Conceptually the methods take and return the invocant type. This
is why I gave them as :(Dog --> Dog) and :(Tree --> Tree) respectively.
That's what I meant with arrow types. But Void as return type is fine
as well. The question is now how these two signatures are merged
together to form the signature required from the class disambiguation.
The glb Dog&Tree to me means that a common supertype be implemented
in the class. Hmm, this at least is what contravariant arrow types
demand. But I'm as of now puzzled if it's not better to require the
class to implement the lub Dog|Tree based on the idea that this makes
instances of the class subtypes of both interfaces. The implementation
of which obliges to the class. Well, invocant parameters are covariant.
So, yes it should be a method on Dog|Tree the type of instances of a
class that does Dog and Tree.


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?

I mean arrow types as used in formal treatments of type systems.
Functions are denoted with arrows there. This is also the reason why
we have the --> in signatures.


Regards, TSa.
--

Reply via email to