NAME
next::XS - Replacement for next::method, performing very fast, and super::method (next-by-mro), with Perl and C++ interface.
SYNOPSIS
use next::XS; # redefines next::method, maybe::next::method and next::can
package MyClass;
sub meth { shift->next::method }
sub meth2 { shift->maybe::next::method }
sub meth3 { return $_[0]->next::can ? $_[0]->next::method : 12 }
sub meth4 { shift->super::meth4 }
sub meth5 { shift->super::maybe::meth5 }
sub on_connect {
my $self = shift;
$async->do_something_and_on_complete(sub {
$self->super::on_connect(); # won't work via next::method
});
}
DESCRIPTION
Perl's next::method
is a powerful tool which is required if you want to use C3 method resolution order. However it is very slow (more than 10 times slower than SUPER::subname
), so that if you actively use it, and your code is fast, it can become slow quite soon.
next::XS
replaces next::method
, maybe::next::method
and next::can
with very fast versions which are nearly the same speed as SUPER::subname
calls and behave exactly as original methods. It replaces them globally even for modules that were loaded before next::XS
.
However using next::
or SUPER::
may lead to inconsistency in your code, because you could use next::method
for a class which didn't say use mro 'c3'
, or use SUPER::
for a class which said use mro 'c3'
. Those methods don't care about your class's method resolution order, they always behave the same. This may lead to hard-to-diagnose bugs in code, for example image hierarchy
A
/ \
B C
\ /
D
where all of classes have (except for A, it just says __PACKAGE__)
sub func { say __PACKAGE__; shift->next::method }
and D
didn't say use mro 'c3'
. For now it's okay, if you run this method, you will see DBCA
as expected. However if you remove methods from class D
and class B
you might expect to see <CA> however instead you will see just A
. That happens because there are 2 separate things: finding the entry method and finding the next method. Finding the first method is always based on the MRO you selected (in our case it's DFS), while next::method
or SUPER::func
always uses certain MRO to find next method.
next::XS
introduces the new keyword <super::subname> which goes to the next method according to MRO of object's class (or the class you initially called method on, in case of Class->method()). So if you replace shift->next::method with shift->super::func, you will see DBA
and A
in our example because MRO is DFS. If you say use mro 'c3'
in class D
, you will see DBCA
and CA
as expected without even changing your code.
However there are more reasons to use that than just a convenience. Using super::
you can write classes that are suitable both for including in C3 hierarchies and simple (DFS) hierarchies.
super::
performs at nearly the same speeds as SUPER::
and upgraded next::
.
Why does super::maybe::
exists? Perl doesn't have SUPER::maybe::
version because it's only for DFS and with DFS you always know if your class has any parents and if they have such a method. However with super::
you don't know which MRO the end class uses, so it might be C3 for which you are not able to detect if there is next method or no if your class is not expected to be the last for that method.
METHODS
next::method ([@args])
Same as Perl's next::method
. Goes to next method using C3
algorithm and croaks if there is no next method (with the same error as original next::method).
maybe::next::method ([@args])
Same as Perl's maybe::next::method
. Goes to next method using C3
algorithm and returns nothing if there is no next method.
next::can()
Same as Perl's next::can
. Returns next method using C3
algorithm or undef if there is no next method.
super::<subname> ([@args])
Goest to next method according to target class' MRO and croaks if there is no next method.
super::maybe::<subname> ([@args])
Goest to next method according to target class' MRO and returns nothing if there is no next method.
C++ FUNCTIONS
See XS::Install to find out how to make headers visible for your module.
#include <xs/next.h>
CV* xs::next::method (HV* target_class, CV*/GV* context_sub)
Returns next method for object/class method of class target_class
considering context_sub
as a subroutine a call to next::method
is into. Always uses C3 algorithm. If there is no next method, returns nullptr
. Example:
void
somefunc (SV* obj, ...)
PPCODE:
...
auto nextsub = xs::next::method(SvSTASH(SvRV(obj)), cv);
...
call_sv((SV*)nextsub);
...
Note that you always have context_sub
inside XSUBs as variable cv
which xsubpp
sets.
See XS::Framework for a more convenient way to call next method.
CV* xs::next::method_strict (HV* target_class, CV*/GV* context_sub)
Same as above but throws std::logic_error if there is no next method (with the same text as perl call would).
CV* xs::next::method (HV* target_class)
CV* xs::next::method_strict (HV* target_class)
Same as above, but automatically detects context subroutine by unwinding the call stack at runtime (what next::method
called from perl does). If you want to go to next method from XSUB then this function is definitely NOT what you want, because XSUBs are not on the calls stack, so that you will simply go to the next method of the closest enclosing native perl subroutine. Always use two-param version function from XSUB. It is used by perl next::method
interface and may be used for rare cases when you know what you want :)
xs::super::method (HV* targret_class, CV*/GV* context_sub)
Returns next method for object/class method of class target_class
considering context_sub
as a subroutine a call to super::subname
is into. Uses MRO of target_class
. If there is no next method, returns nullptr
.
CV* xs::super::method_strict (HV* target_class, CV*/GV* context_sub)
Same as above but throws std::logic_error if there is no next method.
CONTEXT SUB DIFFERENCES
Perl's next::method
unwinds the callstack at runtime to find the enclosing sub. It takes the first not-ANON (and not debugging) sub. So that this case will work:
sub mymethod {
my $self = shift;
my $sub = sub { $self->next::method };
$sub->(); # goes to mymethod's next method
}
However it doesn't work in the following case:
sub mymethod {
my $self = shift;
Benchmark::timethis(-1, sub { $self->next::method });
}
Because at runtime the closest enclosing sub for next::method
call is Benchmark::timeit() your ANON sub is called from. And you get something like "no next::method 'timeit' for 'Benchmark'".
However super::
and Perl's SUPER::
will work fine in the case above, because they use the package where your line SUPER::subname()
is written as a context.
PERFORMANCE
Rate per 1000 executions (i.e. 17305/s means actually 17.3 M/s). Windows 10 x64, Strawberry Perl 5.26.1. Core i7 3.3 Ghz.
Rate perl_nextcan xs_nextcan
perl_nextcan 968/s -- -94%
xs_nextcan 17305/s 1687% --
Rate perl_next_method xs_super xs_next_method
perl_next_method 696/s -- -95% -95%
xs_super 13474/s 1835% -- -5%
xs_next_method 14147/s 1931% 5% --
Rate perl_maybe_next_method perl_maybe_next_method_last xs_maybe_super xs_maybe_next_method xs_maybe_super_last xs_maybe_next_method_last
perl_maybe_next_method 621/s -- -19% -95% -96% -97% -97%
perl_maybe_next_method_last 769/s 24% -- -94% -94% -96% -97%
xs_maybe_super 13740/s 2114% 1686% -- -1% -36% -38%
xs_maybe_next_method 13941/s 2146% 1712% 1% -- -36% -38%
xs_maybe_super_last 21630/s 3385% 2711% 57% 55% -- -3%
xs_maybe_next_method_last 22306/s 3495% 2799% 62% 60% 3% --
AUTHOR
Pronin Oleg <syber@crazypanda.ru>, Crazy Panda LTD
LICENSE
You may distribute this code under the same terms as Perl itself.