In "class interface of roles", Dr.Ruud wrote:
And I was on the line of:
     role R does A | B | C { ... } # unordered composition
     $x does ( A, B, C ) ;         #   ordered composition
     $y does A | B | C ;           # unordered composition

but I would like early and late binding to be explicit.
Maybe the coder will use () around the run-time ones.
Note that compile-time "is" and "does" declarations can be applied
before the curly braces or inside them; if applied inside, each one is
treated as a separate statement:

   class Foo is A is B does R { ... }

   class Foo {
       is A;
       is B;
       does R;
       ...
   }

Unless you put a restriction that the body can have no more than one
"does" statement, I would expect the symmetry between these two cases
to be maintained:

   role R {
       does A;
       does B;
       ...
   }

   role R does A does B { ... }

And since one of the basic principles of multiple role composition is
that ordered composition should not be encouraged, this syntax should
represent unordered composition if it remains at all (and I think it
should).  And if unordered composition can be done this way at
compile-time, it ought to be doable in an analogous way at runtime as
well.

That said, TIMTOWTDI.  I could see "does A | B | C" being used as a
valid alternative to "does A does B does C" on the theory that '|' is
being used like a union operator and 'A', 'B', and 'C' can be viewed
as sets of methods.

One thing which should be considered when deciding whether or not to
permit this sort of syntax is that the availability of a union-ish
operator implies the availability of other set-like operators, such as
"does A & B" or "does A - B".  The problem with these latter
definitions is that, conceptually, the new role is not a superset of
either of the old ones:

   role R1 does A { ... }
   R1.does(A) # true: R1 can do everything that A can.
   A.does(R1) # false: A can't neccessarily do everything that R1 can.

   role R2 does A | B { ... }
   R2.does(A) # true: R2 can do everything that A can.
   A.does(R2) # false: A can't neccessarily do everything that R2 can.

   role R3 does A & B { ... }
   R3.does(A) # false: R3 can't neccessarily do everything that A can.
   A.does(R3) # false: A can't neccessarily do everything that R3 can.

This dissonance comes from the fact that "does" is being used in two
different ways here: as an instruction to help define a class or role
based off of another role or roles, and as a test of whether something
is a semantic superset of a role.  As long as the former only adds to
or changes the roles that it composes, the two concepts are in synch;
but once you allow subsets of roles to be composed, the symmetry
breaks.

And because you have the ability to add methods to R3 that aren't in A
or B, you can't make use of the fact that A & B is a subset of A for
type-checking purposes.

--

Hmm... how about allowing something analogous to binding as the basis
for set-like role composition:

   role Foo ::= A;     # Foo.does(A) and A.does(Foo)
   role Foo ::= A | B; # Foo.does(A) and Foo.does(B)
   role Foo ::= A & B; # A.does(Foo) and B.does(Foo)
   role Foo ::= A - B; # A.does(Foo)

The first case would merely make Foo synonymous with A; nothing special here.

The second case would be equivalent to "role Foo does A does B { }".

The third case would create a new role that includes only those
methods that both A and B have; in addition, it would modify the
definitions of both A and B so that they compose Foo.

The fourth case would include those methods that are in A but not in
B, and would modify the definition of A so that it composes Foo.

These modifications to A and B won't change their existing behavior;
they'll merely add to the range of types that A and B will match.

In all of these cases, you don't get to add functionality to the new
role.  If A and B have methods that conflict, both A | B and A & B
will include an undefined method instead.  If you want to add
functionality, compose Foo into something else and add the
functionality there.

One could try to extend this notion: "A | B" creates an anonymous role
that composes A and B; "A & B" creates an anonymous role that both A
and B compose (for type-checking purposes only); "A - B" creates an
anonymous role that A composes (again, for type-checking purposes
only).  These expressions can be used in "does", or they can be bound
to a name using '::=' (or ':=' at runtime).  If parentheses get used,
you could have something akin to ordered composition, with the
innermost set operators being evaluated first: so "(A | B) | C" would
compose an anonymous role that merges A and B, and would then compose
C into the result - meaning that if A and B have conflicts, C can
resolve them.  So strictly ordered composition could be done as:

  role Foo does (((A) | B) | C) { ... }

making it visually explicit as to what gets done when.  This would
require that '&' and '|' (and possibly '-') be list-associative when
dealing with roles.  I suppose that the comma could be overridden so
that 'A, B' is synonymous with '(A) | B' when A and B are roles; but
then you'd lose the ability to construct lists of roles.  And I
suspect that the above Huffman encoding is more appropriate, anyway.

If you want to define a subset of an existing role as a new role, you
could say something like:

   role Comparator := Num & role {
       method infix:< <= > { ... }
       ...
   }

...and then anything that asks for a Comparator would accept a Num.
Of course, it wouldn't accept a Str; you'd have to say "Num & Str &
role { ... }" for that.

--
Jonathan "Dataweaver" Lang

Reply via email to