On Fri, 21 Jan 2022 17:49:40 +0000 Dave Mitchell <davem@iabyn.com> wrote: > If a framework author wants to break a promise about arity, they could > always call it with an eval: > > if (defined $extra) { > eval { $self->$callback($x, $y, $extra) }; > goto fallback if $@ =~ /Too many arguments/; > } > else { > fallback: > $self->$callback($x, $y); > } > > which is as much (or little) hacky as introspecting the callback to > see if its a sub with a declared arity range. Weelll - except that it will invoke fallback whenever an exception matching that string is thrown from *anywhere* inside the callback. It's not sensitive to just arity checking of $callback itself, but might accidentally trigger many function-calls nested deeply inside it. That's quite a hacky solution indeed ;) Lets step back a little. Signatures work great in a normal "function-call" world wherein the API shape (the interface definition) is given by the definition of the function; the callers simply must adhere to it. Here, we put the API shape on the definition of the function and all works just fine. The situation it doesn't work in is this callback idea; the places where the API shape is defined by the caller of some coderef, and must be adhered to by implementors of those callback functions. Consider in a statically-typed language, where the API signatures of all of these things can be given upfront. Perhaps in that world we'd have a way to define optional vs. required parameters. But as it stands in Perl we don't. In this callback scenario the caller defines the interface implicitly by their action. So far we don't have a way for the caller to say "hey, I'm gonna pass you extra information but I don't mind if you ignore it". This was the motivation behind my original suggestion of some pragma that turns off arity checking from the point of view of the caller: { no fatal 'arity::max'; $self->$callback($item, $idx); # $idx is optional } TEAM on CPAN has provided another hacky solution (this one inspects optrees for the OP_ARGCHECK op and parses out the fields of it): coderef_ignoring_extra($callback)->($self, $item, $idx); https://metacpan.org/pod/Acme::Signature::Arity#coderef_ignoring_extra Of course these solutions aren't very nice, partly because they don't encode the fact that $item must be handled, but $idx may not be. Perhaps we can do better? What would be lovely would be if we could encode a position in a function call site, to say "from this point onwards, don't worry if the invoked sub does not handle this argument. They are optional". I wonder about allowing the keyword `optional` in the middle of a function or method call argument list: $self->$callback($item, optional $idx); A function/method call with one of these 'optional' words inside it is then handled slightly differently. If the invoked sub fails to handle at least all of the arguments before the 'optional' then that's fatal as it is now, but the caller is saying "I don't mind if the callee ignores any of the values after here". Thus $callback could be defined by any of sub ($self, $item) { ... } sub ($self, $item, $idx) { ... } sub ($self, $item, $) { ... } sub ($self, $item, @) { ... } and all would be just fine. ((It would otherwise be a syntax error for 'optional' to appear outside of a function call argument list, or multiple of them, but that's just book-keeping)). This design shape stresses the inversion of the normal design shape of function calls: When dealing with callback scenarios it is the CALLER of the coderef who decides the API shape, not the CALLEE. It is therefore up to the caller to encode what is optional about it. Now I'm not hugely fixed on this particular syntax as a solution to the problem, but I feel that the principle of "caller specifies API" is one we should explore. -- Paul "LeoNerd" Evans leonerd@leonerd.org.uk | https://metacpan.org/author/PEVANS http://www.leonerd.org.uk/ | https://www.tindie.com/stores/leonerd/Thread Previous | Thread Next