Use __SUB__ to get a reference to the current subroutine

What if you want to write a recursive subroutine but you don’t know the name of the current subroutine? Since Perl is a dynamic language and code references are first class objects, you might not know the name of the code reference, if it even has a name. Perl 5.16 introduces __SUB__ as a special sequence to return a reference to the current subroutine. You could almost do the same thing without the new feature, but each of those have drawbacks you might want to avoid.

Although __SUB__ looks like __FILE__, __LINE__, and __PACKAGE__, each of which are compile-time directives, the __SUB__ happens at run time so you can use it with subroutines you define later.

First, consider how you’d try to do this without the __SUB__ feature. You could declare a variable to hold a subroutine reference then in a later statement define the subroutine. Since you’ve already declared the variable, you can use it in the definition. Perl won’t de-reference it until you actually run the subroutine, so it doesn’t matter that it’s not a reference yet:

use v5.10;

my $sub;

$sub = sub {
	state $count = 10;
	say $count;
	return if --$count < 0;
	$sub->();
	};

$sub->();

Your output is a countdown:

10
9
8
7
6
5
4
3
2
1
0

To do that, there are two requirements: the code reference must be stored in a variable, and the variable must already be defined. That’s not always convenient. Not only that, your anonymous subroutine contains a reference to itself, so you’d either have to play games with weak references or just let the reference live forever. Neither of those are attractive.

Rafaƫl Garcia-Suarez solved these problems by creating Sub::Current to give you a ROUTINE function that returns a reference to the current subroutine, even if it is a named subroutine:

use v5.10;
use Sub::Current;

sub countdown {
	state $count = 10;
	say $count;
	return if --$count < 0;
	ROUTINE->();
	};

countdown();

You might want to define these code references as a single statement, even you don’t need to. This is useful for inline subroutines where you want to define the code reference in the parameter list:

use v5.10;
use Sub::Current;

sub run { $_[0]->() };

run( sub {
		state $count = 10;
		say $count;
		return if --$count < 0;
		ROUTINE->();
		}
	);

You may want to define the subroutine in one statement as a return value:

use v5.10;
use Sub::Current;

sub factory {
	my $start = shift;
	sub {
		state $count = $start;
		say $count;
		return if --$count < 0;
		ROUTINE->();
		}
	};

factory(4)->();

Using this module has the disadvantage of a CPAN dependency, although a very light one because it’s self contained. There’s another module, Devel::Caller, from Richard Clamp that can can get a code reference from any level in the call stack, including the current level:

use v5.10;
use Devel::Caller qw(caller_cv);

sub factory {
	my $start = shift;
	sub {
		state $count = $start;
		say $count;
		return if --$count < 0;
		caller_cv(0)->();
		}
	};

factory(7)->();

Perl 5.16 lets you do the same thing without the CPAN module:

use v5.15.6;  # until v5.16 is released

sub factory {
	my $start = shift;
	sub {
		state $count = $start;
		say $count;
		return if --$count < 0;
		__SUB__->();
		}
	};

As with many new features added since Perl v5.10, you can enable __SUB__ with a use VERSION statement,
as you see in the previous example, or with the feature pragma and the current_sub import:

use feature qw(say state current_sub);

sub factory {
	my $start = shift;
	sub {
		state $count = $start;
		say $count;
		return if --$count < 0;
		__SUB__->();
		}
	};

factory(7)->();

Things to remember

  • Perl v5.16 provides the __SUB__ directive to return a reference to the currently running subroutine
  • Import this new feature by requiring the Perl version or through
    the feature pragma

  • Prior to Perl v5.16, you can do this the same thing with Sub::Current

Post to Twitter Post to Delicious Post to Digg Post to Facebook Post to Reddit

Leave a comment

0 Comments.

Leave a Reply

You must be logged in to post a comment.