should roles be parameterized?

2005-10-28 Thread Christopher D. Malon


On Oct 28, 2005, at 11:13 PM, Luke Palmer wrote:


Most certainly.  Implicit in a role or a theory is its algebra (though
we've talked about QuickCheckish ways to make it explicit).   For
instance, technically the VectorSpace theory only requires you to
define identity, addition, and scalar multiplication.  However, in
order to honestly do VectorSpace, addition has to be commutative,
which is something that the compiler usually is not able to check.


Trying to think through the VectorSpace example, and a slightly
more complicated example (a Field), I'm starting to wonder whether
roles need to be parameterized somehow.  (Maybe they already are?
This is one of my first p6 attempts, so bear with me...)

For the non-mathematically inclined:
A field is a set with two binary operations, + and *.
Under either operation (+ or *), the set is an abelian (=  
commutative) group,

and a field has a distributive property: a * (b + c) = a*b + a*c.
An easy example is the set of real numbers, with + being addition
and * being multiplication.

Now, I'd really like to say something like:

class RealNumbers does Field(addition => infix:<+>, multiplication =>  
infix:<*>);


role Field {
  does AbelianGroup(operation => addition);
  does AbelianGroup(operation => multiplication);
}

Properties such as the distributive property are implicit
when a user asserts something is a Field, but the compiler
won't know about them.

To summarize so far:
1. The problem here is that a class has two distinct ways of
fulfilling the Group role, using methods that are named differently.
I want to keep these disambiguated, while benefitting from whatever
code comes for free when my class does Group.

2. The proposed solution is that, in order to "do" a role, I get to
provide a hash that describes, to the Role code, how it should
define its interfaces in terms of my class's variables and methods,
instead of assuming it will be able to look for particular names.

Now, the problem:

role Group should define a method "inverse()" whose implementation
is up to the class.  inverse(operation => '+') or inverse(operation  
=> '*')

would make perfect sense.  On an object that does Group only one way,
inverse() by itself would be okay, but it would be ambiguous
on a Field object.  I don't know whether it's better for the interpreter
to produce a run-time error when a class implements a role in two ways
and the parameter isn't supplied, or if one implementation of a role  
should

take priority.

Does this problem make sense, and is it solved already?

Christopher



filling a role different ways

2005-11-18 Thread Christopher D. Malon
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 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.

Christopher