Skip to content

Instantly share code, notes, and snippets.

@skids
Created November 5, 2012 01:47
Show Gist options
  • Save skids/4014812 to your computer and use it in GitHub Desktop.
Save skids/4014812 to your computer and use it in GitHub Desktop.
Perl6 diamond inheritance interim workaround
This trick can be used to temporarily work around some NYI areas of Perl6 Parametric Roles for a specific use case.
A known shortfall in the current Parametric Role implementation is NYI diamond composure:
role A { method foo ($x) { "HIYA".say } }
role B does A { }
role C does A { }
role D does B does C { }
class E does D { }
my E $e .= new();
$e.foo(1); # ===SORRY!=== Method 'foo' must be resolved by class E because it exists in multiple roles (B, C)
By spec, role A should only be considered to be composed once, so this conflict should not happen.
This workaround assumes you do not want to override the method foo using roles B or C, in which case you have
a genuine diamond problem and have to resolve it in the bottom class/role.
First we try to define the foo method as a multi, so a an actual conflict cannot happen -- copies of the foo
method just get added to the candidate list:
role A { multi method foo ($x) { "HIYA".say } }
role B does A { }
role C does A { }
role D does B does C { }
class E does D { }
my E $e .= new();
$e.foo(1); # Ambiguous call to 'foo'; these signatures all match: :(E : $x, Mu *%_) :(E : $x, Mu *%_)
So now at least it fails at runtime, rather than at composure time. So how do we get it to choose one or the
other candidate? We can't add an "is default" in role A, because both candidates would get it. Well, recently,
more fully spec-compliant support for candidate tie-breaking was added to rakudo. When you have a multi-method
with candidates that are identical except for a runtime constraint, candidates with constraints are evaluated
in the order in which they were declared. The first time a constraint is true, that candidate is chosen.
So it turns out to be as simple as this:
role A { multi method foo ($x where { True } ) { "HIYA".say } }
role B does A { }
role C does A { }
role D does B does C { }
class E does D { }
my E $e .= new();
$e.foo(1); # HIYA
\o/
This will work so long as you do not expect users to override foo themselves.
Now, for attributes, we can use a role-scoped hash and key off $self.
role A {
my %attrs;
multi method naval ($self where {True}:) is rw {
%attrs{$self}<naval> //= "innie";
%attrs{$self}<naval>;
}
}
This will of course leak memory, because the hash entries don't get released
when objects are discarded, but if all you want to do is prototype until
the diamond problem is fixed, it does the trick.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment