Signature restriction in roles in raku

throw some warning with the implemented signature of the role L.

You get that if you prefix the method declaration with multi:

role L {
  multi method do-l (Int, Int --> Int ) { ... }
}

With this your program displays:

===SORRY!=== Error while compiling ...
Multi method 'do-l' with signature :(A: Int $, Int $, *%_ --> Int)
must be implemented by A because it is required by a role ...

I'd like to know if there is a good reason why this code should compile [without the multi]

I think the design intent was to support two notions of polymorphic composition:

  • Without the multi, enforcement only relates to existence of a method with the right name; parameters are ignored.

  • With the multi, enforcement covers the name and all parameters as well (or some).

My personal take on whether there's a good reason for:

  • Supporting two flavors of method polymorphism? Sometimes enforcing strict adherence to the signature is helpful. Sometimes it gets in the way.

  • Distinguishing them via multi? Full signature enforcement requires that implementing classes/roles have a method with exactly the same signature. But what if an implementing class/role wants to handle an int instead of Int for a parameter? Raku compromises. Provided an implementing class/role has an exactly compliant method it can also have variations. The perfect way to convey this is to prefix a stubbed method with multi.

  • Having the default be name only polymorphism? We could have chosen multi semantics as the default, and had users write an only prefix if they wanted name only polymorphism. But that would reverse the usual situation (i.e. ignoring stubbed methods). More generally, the intent is that Raku provides a wide range of stricture for its features, from relaxed to uptight, and picks a default for any given feature that is judged right based on feedback from users over the years.

What if the default doesn't seem right? What if the existing range of strictures isn't enough? What if one group thinks we should go left and another thinks we should go right?

Raku has (imo) remarkable governance mechanisms for supporting user driven language evolution. At the top level there are elements like the braid architecture. At the bottom level there are elements like versionable types. In the middle are elements like RoleToClassApplier which mediates the process of applying a role to a class, which is the point at which a required method needs to be found or the class's construction will fail. In short, if the language doesn't work the way you want, including such things as strictures, you can, at least in principle, change it so it does.


I'm assuming here you're asking about why there's no warning with respect to the stubbing. Indeed, normally a stubbed method must be implemented — but that's it.

You can see how the roles are composed into class here in Rakudo's source ($yada basically means $is-stubbed):

if $yada {
    unless has_method($!target, $name, 0)
            || $!target.HOW.has_public_attribute($!target, $name) {
        my @needed;
        for @!roles {
            for nqp::hllize($_.HOW.method_table($_)) -> $m {
                if $m.key eq $name {
                    nqp::push(@needed, $_.HOW.name($_));
                }
            }
        }
        nqp::push(@stubs, nqp::hash('name', $name, 'needed', @needed, 'target', $!target));
    }
}

So you can see that the logic is just to see if a method exists with the same name. It's definitely possible to write a module that would update this logic by augmenting the apply() method or outright replacing the RoleToClassApplier class. However, it might be difficult. For example, consider:

class Letter { }
class A is Letter { } 
class B is Letter { }

role Foo {
  method foo (Letter) { ... }
}

class Bar does Foo { 
  method foo (A) { 'a' }
  method foo (B) { 'b' }
}

Should we consider the stubbed method to be correctly implemented? But someone else could later say class C is Letter and suddenly it's not implemented. (Of course, we could say that the best logic would be require, at the least, the identical or supertype, but that's more restictive than necessary for dynamic languages, IMO).

There isn't, AFAICT, a method that's called on roles during composition that also references the class (actually, there isn't any method called at all in add_method), so there's no way to write your own check within the role . (but I could be wrong, maybe raiph, lizmat or jnthn could correct me).

My recommendation in this case would be to, instead of stubbing it, simply add in a die statement:

role L {
  method do-l(Int $a, Int $b --> Int) {
    die "SORRY! Classes implementing role L must support the signature (Int, Int)";
  }
}

This won't capture it at compilation, but if at any point another method in L needs to call on do-l(Int, Int) —even if the composing class implements other signatures—, it will get called and the error caught fairly quickly.

Tags:

Raku