I'd like to see a way to map methods and variables expected by a role, onto
methods and variables provided by a class.  I'd like it to be possible
for a class to provide several such maps, having the appropriate one
selected according to context.

Continuing an example from an earlier thread <http:// www.nntp.perl.org/group/perl.perl6.language/23909>,
you might want:

class RealNumber does Group;

but it might do it through addition or multiplication (where zero is
exceptional in the multiplication provision for the role).

Then, as in Luke's example, you'd want a subroutine "power",
dispatched by the role Group

power(4, 3) # computes 4 to the third power, with respect to group operation

to return 64 in a multiplication Group context, but 12 in an addition one.

Here's a fairly simple proposal to make this work,
with three changes to the language:

1. Every class and instance would have a special hash attribute:

    %.?HOW is rw

to be keyed on role names, and to be given method values,
including "undef".  These values would be dynamically scoped;
an assignment to &.?HOW{$role} would implicitly carry "temp."
So, you can set the %.?HOW context to something appropriate,
either for a particular variable or the whole class, right before
you call a sub that requires you to pick which way you're going
to fill the role.

(The class attribute needs a different name, maybe %.?USUALLY,
since class and instance attributes share a namespace.)

2. Anywhere something is expanded to:

    ::T <= Role{::T}

the ::T will no longer represent a Class,
but a pair (Class, Method), with "undef" allowed for the method.

So, you can't put a real number locally bound to '+' and one bound to '*'
together in a context that requires objects that all come from the
same group.

3. A new keyword "through" would be added to the "does" syntax.
When you say:

    class RealNumbers {
        does Group through &.times;
        does Group through &.plus;

you're saying that the pairs ::T = (RealNumber, &.times)
and ::T = (RealNumber, &.plus) satisfy the constraint Group{::T}.
If you say "does" without the "through" keyword, you're telling
the dispatcher that (RealNumber, undef) satisfies the constraint.

When a subroutine is to be dispatched by a role Bar,
and an instance $x of class Foo, where Foo does Bar (possibly
several ways), is provided, the dispatcher looks up the instance
value &x.?HOW{Bar}.  If it's undef, it looks at the class value
&Foo::?HOW{Bar}.  The method &.?HOW{Bar} is invoked on $x
prior to entry into the routine.  (If the instance and class values
of &.?HOW{Bar} are both undef, then it just plunges in,
so anything you could write before, still works after this
language change.)

So I could write:

     role Group {
          # requires:
method compose (::?CLASS $operand) { ... } # product of $self and $operand
          method inverse () { ... } # invert $self
          has $.identity;

     sub power (Group $element, Int $pow) {
        given $pow {
            when $_ <  0  { power(inverse($x), -$pow) }
            when $_ == 0  { $.identity }
            when $_ >  0  { compose($x, power($x, $pow - 1)) }

    class Integer {
does Group; # okay, just define power, identity, and compose inside

and yet

     class RealNumber {
         has num $.x;

         method infix:<+>(RealNumber $y) { $.x + $y.x }
         method infix:<*>(RealNumber $y) { $.x * $y.x }
         method inverse (:$op) {
              given($op) {
                       when '+' { 0 - $.x }
                       when '*' { 1/ $.x }
         has $.identity;

         method plus() {
             temp &.compose = &.infix:<+>;
             temp &.inverse = &.inverse.assuming(:op('+'));
             temp $.identity = 0;
         method times(); # similar

         does Group through &.plus;
         does Group through &.times;

     my RealNumber ($a, $b);

     &.?HOW{'Group'} = &.plus for ($a, $b);

     ($a, $b) = (4, 3);
     my $c = power($a, $b);
     say $c;   # 12

The alternative to the routines "plus" and "times" would be
to make all of RealNumber's Group role routines read
the %.?HOW hash individually.  That would seem to work
without any language modifications whatsoever, but:

1. It would put ugly case-checking in too many places, IMHO.
2. Uses of conflicting role providers wouldn't be detected.
3. If you're importing someone else's class, and the class's
author didn't know about the role but you do, then you should be
able to say "Pigs really can fly, and this subroutine says
how they do it" without rewriting all the class method definitions.


Reply via email to