TSa wrote:
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.

Actually, the correct equal method for the composed class needs to
check the sides of the square, because how the class defines it: in
this example, class GenSquare _is_ the final class, and so whatever
method it provides _is_ the final method, for good or ill.  If this
results in other method definitions inherited from GenPointMixin
behaving strangely, then GenSquare needs to override those methods as
well.

Mind you, the above implies a radical shift in the underlying
semantics, akin to having class Dog compose role Tree and redefining
method bark() to suit its own nature.  Proper use of role composition
is to refine the role's underlying concept, not to radically alter it.
Even if we were to go with a modified version of the original
example, where GenSquare is a role and both GenSquare and GenPoint are
being composed into a final class - say, "GenPositionedSquare" - you
still have the problem that the final class is conceptually supposed
to be both a GenSquare and a GenPoint at the same time.  If the
underlying concepts behind the composed roles are fundamentally
incompatable (such as a Dog and a Tree), you're going to have
problems.

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.

This is what happens by default; no special syntax is needed.

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

Exactly.

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

Perl has yet to implement a telepathic compiler, so the compiler can
only make best guesses as to what the programmer intended.  For now,
we have to make some concessions to reality; one such concession is
that the most specific thing should have authority over (and
responsibility for) the less specific things.  The thing being
composed into is by definition more specific than the thing being
composed when it comes to implementation; thus, the latter should be
given free rein to redefine the methods provided by the former - and
if things go wrong, you should blame the latter rather than the
former.  Letting a role override a decision made by a class that it's
composed into means that there's something about the role's
implementation that the class cannot touch.

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

It is a fact that class inheritance affects the behaviour of a class;
it cannot be disregarded when considering type.

That said, class inheritance (with respect to its semantic differences
from role composition) is an area where my understanding is rather
shaky.  To me, the whole notion of inheritance is a bit too typeish
for my tastes; I try to handle _all_ code reuse by means of roles,
with classes only being brought into play when I want to be able to
instantiate an object.  That is, I tend to define what an object is
exclusively by what it does.

BTW, whoever writes the Perl 6 reference book has a golden opportunity
to highlight a bit of conceptual elegance.  In perl 6, there are three
ways to handle code reuse: "is" (class inheritance), "does" (role
composition), and "has" (attribute delegation).

Or do you want constraints on the class derivation process that
guarantees subclasses to be subtypes?

I'm not sure what you mean here.  In particular, Perl uses "subtype"
in a very specific way - and which is antithetical to "subclass".
roles and subclasses expand an object's capabilties; subtypes restrict
them.

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.

What do you mean by "replaceability of subclasses"?

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

I'm with you so far...

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.

That's where you're losing me.  I don't see how "blind" precedence to
class methods would cause the results that you describe.


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

No; it's $self with the sigil.  $self is the scalar that contains the
invocant.

> [..]
>    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?

Note the single quotes around C<A::foo>.  'A' isn't referring to
anything; it's literally part of the name.

The exclamation mark indicates that I'm accessing a private method.
Likewise, the 'my' means that I'm _defining_ a private method.

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

I still don't see a purpose for it.

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.

OK; I just tried to decipher your original example.  If I'm
understanding it correctly, what you're actually wanting to say is
this:

role GenEqual
{
  method equal( GenEqual $p --> Bool ) {...}
}


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

class GenSquare does GenPoint does GenEqual
{
  has Int $.side;
  method equal ( GenSquare $p --> Bool )
  {
     return $self.GenPoint::equal($p) and $self.side == $p.side;
  }
}

Note that GenPoint no longer makes any reference to GenSquare.

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

They don't have to; and the duck-typing problem occurs most
prominantly when they don't.

The question is now how these two signatures are merged
together to form the signature required from the class disambiguation.

Signatures shouldn't be merged.

--
Jonathan "Dataweaver" Lang

Reply via email to