Is there a way to automate this for example with some decorator kind of thing?
Because this is Raku where there's always More Than One Way To Do It, there are in fact multiple ways to fairly easily prevent the automatic *%_
method parameter. I'll show you two (and explain how they work) and then try to convince you that, in fact "%_
is actually a very useful thing in object oriented Raku programming".
The answer: rejecting unexpected named parameters (the *%_
)
Consider this code:
class C0 { method m { say 'm called' } }
class C1 { method m(*% ()) { say 'm called' } }
class C2 is hidden { method m { say 'm called' } }
my $c = C0.new;
$c.m(:unexpected); # OUTPUT: «m called»
C0
is a standard class and, as you've already noted, it happily accepts unexpected named arguments. But if we try the same thing with C1
, we'd get this error:
Unexpected named argument 'unexpected' passed in sub-signature
And, if we tried it with C2
, we'd get this very similar one:
Unexpected named argument 'unexpected' passed
The biggest difference for our current purposes is that C1
just disallowed unexpected named arguments for m
– we could create other C1
methods that do accept unexpected named arguments. Conversely, with C2
, we prevent any of the classes methods from accepting unexpected named arguments (and do a bit more, but more on that soon).
How do these each work?
C1
C1
prevents m
from accepting any unexpected arguments by using named slurpy parameter and then destructuring that parameter into a sub-signature – in this case, a sub-signature that happens to consist of an empty list.
Breaking that down a bit, consider this code:
sub f(:$group-name!, *@ids (Int, Int?, Int?, Int?) {}
That signature is saying "I have to have a :group-name parameter and some number of @ids. And those @ids must have at least one Int, and could have up to four Ints – but no more than four, and no non-Ints)". So we can call f
with :group-name<foo>, 1
. Or with :group-name<foo>, 1, 42, 55
. But if we tried to call it with :group-name<foo>, 1, 42, 55, 9, 9, 9
, we'd get the error: Too many positionals passed to 'f'; expected 1 to 4 arguments but got 6 in sub-signature of parameter @ids
.
So what's the signature in C1
's m
method saying. Well, the *%
part says "I'll take a list of absolutely any named argument at all" and then the ()
part adds "so long as that list doesn't have anything in it!".
And that gets us exactly what you were looking for: a method that rejects unexpected named arguments (and expected named arguments don't pose any problem at all – you'd declare those before the *%
, and they'd be handled before ever reaching the sub-signature.
C2
C2
is both simpler and, at the same time, a bit more overkill. C2
uses the hidden
trait which stops C2
from participating in the redistpatch process – and, as a side effect, prevents C2
's methods from being given an automatic *%_
parameter.
Explaining the full scope of Raku's (re) dispatch process is beyond the scope of this answer but I'll just say that, depending on what you want, that may be exactly what you're looking for or it may be an unwanted consequence.
But you should (maybe) ignore those answers
Let's go back to how you ended your ques ion. You asked whether you missed something that makes *%_
"actually a very useful thing in object oriented Raku programming". I'd argue that, yes, the automatic addition of *%_
is in fact very helpful.
There are a bunch of different ways to come at why that's the case but, since you mentioned object-oriented Raku, I'll come at it from that angle (I personally write more functional-programming Raku, but the both have there place).
So from an OOP perspective, the addition of *%_
makes it much easier to write Raku methods that follow the Liskov_substitution_principle (aka, the L
in SOLID OOP).
I'm not sure how familiar you are with that principle, so I'll say just a few words about it (apologizes if I'm belaboring the obvious). The LSP says that "Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program".
That can be a bit abstract, so here's a Raku example that's … ok, still kind of abstract, but bear with me.
So, consider the Raku Stash
class. It doesn't matter what it does right now; I'm bringing it up because of its type graph:

So a Stash isa Hash
and a Hash isa Map
and a Map isa Cool
and a Cool isa Mu
. So that means that any function that's type-constrained with any of Stash
, Hash
, Map
, Cool
, or Mu
will accept a Stash
– and it's up to Stash
to make sure that it can be validly passed to any such function without breaking anything. Of course, for the most part a Stash
handles this through inheritance: most of the methods that you can call on a Stash
aren't actually defined on the Stash
– they're inherited and thus are automatically compatible with the superclass method (because they are the superclass method).
But, for methods that Stash
chooses to overwrite, it's part of Stash
's job to make sure that it doesn't break any superclasses expected behavior. And the same goes for, say, Map
– but even more so. If there's a method on Cool
that Map
overrides, Map
needs to think not only about Map
's callers but also about Stash
's callers: after all, Stash
might not override that method. So Map
needs to make sure that it can handle anything Cool
wants/needs to handle – and that's something that, in theory, could happen at any time. (In practice, of course, Cool
is pretty stable and both it and Map
are part of Rakudo. But the same thing would apply for 3rd-party classes, where APIs change more rapidly and you may not have the ability to coordinate changes.)
In particular, "handling anything Cool
can handle" means being able to be called with the arguments callers of Cool
passed without throwing type errors – even when those arguments might change.
And that's where *%_
comes in. Unless one of them opts out, every member of the method chain accepts any named parameter. As a result, Cool
can freely add new named parameters, and Map
/Stash
's callers (who, after all, aren't supposed to care or need to care whether which object they're calling) can pass in the corresponding named arguments. And, even if those method calls get resolved by a method that's never heard of the new argument, absolutely nothing will break.
Of course, if it just gets eaten by *%_
, the new argument won't actually do anything, which (depending on the class/method) might not be ideal. But that's where re-dispatching comes back in and lets the intervening class act on the method and still pass it on to a superclass. But, again, that's getting out of scope.
So, after a long answer to a short question, the bottom line is that you can prevent a method from accepting unexpected named arguments. But, depending on your approach to programming/Raku, there are some very good reasons to consider not doing so.