On Tue, 24 Jan 2023 01:07:23 +0100
Graham Knop <haarg@haarg.org> wrote:
> The problem is not limited to nomethod. Overloads in general have a
> defined signature where each argument has a specific meaning, no
> matter what is invoked.
Isn't that just somewhat coïncidental, because up until now we've only
had unary or binary operators in here. substr is the first thing which
breaks that pattern, having 3 (or 4) positional arguments to it, but
only being unary dispatch on the first of those. The second two (or
three with replacement) are just extra information that doesn't take
part in the dispatching decision.
> 1. The object, as the invocant.
> 2. The second argument to the op.
> 3. A swap parameter. For a binary op, if the first arg did not have an
> appropriate overload, the invocant and next argument will be swapped
> and this will be true.
> 4. If invoked as a nomethod fallback, the original overload that was
> attempted. 5. If using a bitwise op, and the "bitwise" feature is
> enabled, a true value.
>
> The second argument and swap option are passed even for unary
> overloads.
>
> Based on this pattern, the substr overload parameters of position and
> length would need to be the 6th and 7th argument. This doesn't really
> seem reasonable.
The whole situation feels slightly reminiscent of the trouble with
things like caller() and stat() [the latter of which is largely fixed
by File::stat]. Namely, that we have an API shape which is defined in
terms of a fairly ad-hoc list of positional values, and it becomes
difficult to extend the information provided by just giving more values.
If we were to start again from scratch, I'd suggest what we'd probably
come up with is an API shape wherein the first (non-invocant) value
passed in to any of the callbacks is an instance of some "overload
context" object, followed by all of the remaining arguments.
use overload
"+" => sub ($self, $ctx, $other) { ... }
"sin" => sub ($self, $ctx) { ... }
"substr" => sub ($self, $ctx, $pos, $len) { ... }
"nomethod" => sub ($self, $ctx, @args) { ... }
etc...;
Such a context object would then have some methods on it to get
$ctx->operator_name # or maybe something shorter
$ctx->is_swapped
$ctx->is_numerical
etc...
much easier to extend the available information later on by just adding
new methods to that context object. Since it's always just one
positional argument to any of the callbacks, it doesn't get in the way
of extra arguments later on.
Now of course, we don't live in that world. Currently. But I wonder if
we can somehow get there from here.
First (and most radical) idea: some sort of marker/version/indicator on
the `use overload` to say "Hey I'm using this fundamentally new and
totally different API shape".
use overload
newapi => 1, # deargod we need a better name here ;)
nomethod => sub ($self, $ctx, @args) { ... } # is now all fine
It'd allow newly-written code to work with this new structure, but it's
not the most version-friendly approach because overload.pm isn't
dual-life and no older versions would recognise this. So we'd be stuck
having to write code only for perl 5.38+ if we wanted to use this.
Perhaps it could be solved by a dual-life-capable "overload::v2" which
is shipped by newer perls and can be CPAN shimmed around older ones:
use overload::v2
nomethod => sub ($self, $ctx, @args) { ... };
Second idea: we could observe that currently most code that does look
up the operation name just treats it as a plain string. If this new
context object had stringification overloading on it, then the object
itself could now be passed in the position where currently a plain
string turns up, and most code should cope just fine:
use overload
nomethod => sub ($self, $other, $swapped, $opname) {
if($opname eq "+") { ... }
};
That'd at least let us pass this "extra information" object in to all
the callbacks, allowing e.g. the substr one to find its additional
arguments that way:
use overload
substr => sub ($self, $, $, $ctx) {
my $pos = $ctx->substr_pos;
my $len = $ctx->substr_len;
...
};
I don't know. That's kinda icky, and isn't very nice longer-term. It
feels like that's the "smallest reasonable step" solution to get us out
of where we are now, to permit this. But I think I would prefer a nicer
API shape as given in the "First idea", even if it's a harder step to
take from where we are.
Where we are now isn't a reasonable place to start from, so we
shouldn't feel too bad if it's a painful step to get from here to
somewhere more sensible.
--
Paul "LeoNerd" Evans
leonerd@leonerd.org.uk |
https://metacpan.org/author/PEVANS http://www.leonerd.org.uk/ |
https://www.tindie.com/stores/leonerd/