This morning we had another discussion on the ordering of roles. This is a follow-up to a discussion with Ovid back in January, which I cannot find quickly.
As I understand Ovid’s original complaint, it was that given
role A { method foo { ... } } role B does A { method foo { ... } } class C does B { ... }
C
gets B::foo
, but in
role B { method foo { ... } } role A does B { method foo { ... } } class C does A { ... }
C
gets A::foo
. As I understand, Ovid sees this as roughly equivalent to the difference between role D; role E; class F does D does E
and role D; role E; class F does E does D
. To my mind they are completely different things, and if someone re-orders role “inheritance” without considering the consequences, they deserve what they get.
Personally, I think using A
and B
here obscures what’s going on, so let me switch to a real world example, straight (though simplified) from Rakudo’s source:
role Numeric { method log { ... } # very general support for log } role Real does Numeric { method log { ... } # Real-specific support for log } class Int does Real { ... }
When I put like this, clearly in Real does Numeric
the direction of the does
is of utmost importance — every Real
is Numeric
(which is the general p6 role for all numbers, including Real
s and things like Complex
), but not every Numeric
is Real
. It is obvious that it makes sense for Real::log
to override Numeric::log
as the log that Int
gets.
It has been suggested that we should have a new keyword claim
for this. Using it, the example from Rakudo becomes
role Numeric { method log { ... } # very general support for log } role Real does Numeric { claim method log { ... } # Real-specific support for log } class Int does Real { ... }
It is more explicit about what is going on, and allows us to generate an error (or maybe warning?) if you leave the claim out. I have no big objection to this idea, doing this sort of thing explicitly is a pretty good idea. However, it seems to me that as a solution to this (rather than just a nice notification to the programmer) it is very weak. For my example, consider an additional (simplified) part of the Rakudo role hierarchy (but a fake method):
role Numeric { method log { ... } # very general support for log } role Real does Numeric { claim method log { ... } # Real-specific support for log } role Rational does Real { claim method log { $!numerator.log - $!denominator.log } # cleverly save a divide! } class Rat does Rational { ... }
Now we have two claims. How do we determine which one to honor? masak++ thinks it is the “most-derived” one. That’s certainly the answer I’d want, too — but now look at what has happened here. In our very first example, we had two method log
, and we added claim
to establish which had priority. In this most recent example, we have two claim method log
, and so we’ve had to add a notion of “most derived” to distinguish between them. But of course, the notion of “most derived” was all we needed to solve the first problem!
In summary: We have to have a “most derived” notion for roles, or else we limit a lot of really useful behavior. We certainly can add claim
to help eliminate confusion on programmer’s part, but we need to understand that it will only catch the most obvious cases.
PS I don’t know if skipping the divide in log is actually worthwhile on modern computers. But the point that it makes that people might want to have a longish chain of role inheritance is what is important here.
March 9, 2015 at 10:05 pm |
[…] written a drive-by summary/followup thoughts on “the role debate”, after colomon++ had a blog post about it. Food for thought, indeed. Comments welcome, of […]